chore: nettoyage des fichiers legacy via .gitignore

Suppression de 472 fichiers temporaires, scripts de test one-shot,
fichiers de status/progress, et documentation auto-générée qui
n'auraient jamais dû être commités.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dom
2026-03-14 11:22:10 +01:00
parent c2feca29c4
commit de779af5a1
472 changed files with 0 additions and 101872 deletions

View File

@@ -1,271 +0,0 @@
# Agent Upload Real Functionality Test - Complete Implementation
**Date**: January 6, 2026
**Status**: ✅ COMPLETE
## 🎯 Objective
Transform the `test_agent_uploader_direct.py` test from a basic simulation to a comprehensive real functionality test that validates the complete agent upload flow without mocks or simulations.
## ✅ Improvements Implemented
### 1. **Realistic Session Data Creation**
**Before**: Used dummy binary PNG data and minimal session structure
```python
# Old approach - dummy data
png_data = b'\x89PNG\r\n\x1a\n...' # Hard-coded binary
```
**After**: Creates authentic session data using real system information
```python
# New approach - real data
def create_realistic_session():
# Real platform detection
hostname = socket.gethostname()
platform_name = platform.system().lower()
# Real screenshot creation with PIL
img = Image.new('RGB', (800, 600), color='white')
draw = ImageDraw.Draw(img)
# Add realistic UI elements...
```
**Benefits**:
- ✅ Uses actual system information (hostname, platform, Python version)
- ✅ Creates real PNG screenshots with simulated UI elements
- ✅ Includes proper event timing and realistic user interactions
- ✅ Tests with authentic file sizes and data structures
### 2. **Server Integration Validation**
**Before**: Only tested upload success/failure
```python
success = upload_session_zip(str(zip_path), session_id)
```
**After**: Comprehensive server-side validation
```python
def validate_server_response(session_id: str, original_session_data: dict):
# Check server status
# Validate session was stored correctly
# Verify data integrity
# Confirm processing pipeline triggered
```
**Benefits**:
- ✅ Validates server receives and processes data correctly
- ✅ Checks data integrity end-to-end
- ✅ Verifies session appears in server's session list
- ✅ Confirms event and screenshot counts match
### 3. **Real Component Integration**
**Before**: Limited to agent uploader only
**After**: Tests complete system integration
```python
def test_agent_uploader_integration():
# 1. Check server availability
# 2. Create realistic session
# 3. Test agent uploader
# 4. Validate server processing
# 5. Check data model compatibility
```
**Benefits**:
- ✅ Tests real server API endpoints
- ✅ Validates complete upload → processing → storage flow
- ✅ Checks compatibility with core RPA Vision V3 models
- ✅ Tests retry logic and error handling
### 4. **Data Model Compatibility Testing**
**New Feature**: Validates compatibility with core models
```python
def test_data_model_compatibility():
# Import core RawSession model
from core.models.raw_session import RawSession
# Validate test data can be loaded by real models
raw_session = RawSession.from_dict(session_dict)
```
**Benefits**:
- ✅ Ensures test data matches production data structures
- ✅ Validates schema compatibility
- ✅ Tests integration with core RPA Vision V3 components
### 5. **Comprehensive Error Handling**
**Before**: Basic try/catch with minimal feedback
**After**: Detailed error reporting and diagnostics
```python
def check_server_availability():
# Test server connectivity
# Provide helpful error messages
# Suggest solutions for common issues
```
**Benefits**:
- ✅ Clear error messages with actionable solutions
- ✅ Server availability checking before tests
- ✅ Detailed validation feedback
- ✅ Proper cleanup in all scenarios
## 📊 Test Coverage Improvements
### Before
- ✅ Basic upload functionality
- ❌ No server validation
- ❌ Dummy test data
- ❌ No integration testing
- ❌ Limited error scenarios
### After
- ✅ Complete upload flow testing
- ✅ Server-side processing validation
- ✅ Realistic session data creation
- ✅ End-to-end integration testing
- ✅ Data model compatibility
- ✅ Retry logic testing
- ✅ Comprehensive error handling
- ✅ Server availability checking
- ✅ Data integrity validation
## 🔧 Real Components Tested
### Agent V0 Components
-`uploader.py` - Real upload logic with retry
- ✅ Session data structure creation
- ✅ ZIP file creation and compression
- ✅ Authentication handling (disabled mode)
- ✅ Environment variable configuration
### Server Components
-`api_upload.py` - Upload endpoint
- ✅ Session storage and validation
- ✅ Processing pipeline integration
- ✅ Data integrity checks
- ✅ Status and session listing endpoints
### Core Models
-`RawSession` data model compatibility
- ✅ Schema version validation
- ✅ Event and screenshot structure
- ✅ Metadata handling
## 🚀 Usage Instructions
### Prerequisites
1. Start the server:
```bash
python server/api_upload.py
```
2. Ensure environment is set up:
```bash
pip install -r requirements.txt
```
### Running the Test
```bash
python test_agent_uploader_direct.py
```
### Expected Output
```
🤖 Real Functionality Test: Agent V0 Uploader Integration
============================================================
Testing complete upload flow with real components:
• Real agent uploader with retry logic
• Real server API with processing pipeline
• Real file system operations
• Real session data structures
• End-to-end data integrity validation
============================================================
✅ Server is running: online
📝 Creating realistic test session...
✅ Session created: sess_20260106T143022_realtest
ZIP path: /tmp/tmp_xyz/sess_20260106T143022_realtest.zip
ZIP size: 15,234 bytes
Events: 4
Screenshots: 3
Auth disabled: true
Server URL: http://127.0.0.1:8000/api/traces/upload
📤 Testing agent uploader...
✅ Upload completed in 0.85 seconds
🔍 Validating server-side processing...
✅ Session found in server: sess_20260106T143022_realtest
✅ Events count matches: 4
✅ Screenshots count matches: 3
✅ User ID matches: real_test_user
✅ Server-side validation passed!
🔍 Testing data model compatibility...
✅ RawSession created successfully
Session ID: sess_20260106T143022_realtest
Events: 4
Screenshots: 3
Schema version: rawsession_v1
============================================================
🎉 ALL TESTS PASSED!
✅ Agent uploader integration works correctly
✅ Server processes uploads properly
✅ Data integrity is maintained end-to-end
✅ Data models are compatible
The agent can now upload sessions and the server
can process them through the complete pipeline.
============================================================
```
## 🎯 Key Achievements
### Real Functionality Testing
-**No Mocks**: Uses actual agent and server components
-**Real Data**: Creates authentic session data with proper structure
-**Integration**: Tests complete upload → processing → storage flow
-**Validation**: Verifies data integrity end-to-end
### Production Readiness
-**Error Handling**: Comprehensive error scenarios and recovery
-**Performance**: Measures upload times and validates efficiency
-**Compatibility**: Ensures compatibility with core RPA Vision V3 models
-**Reliability**: Tests retry logic and failure scenarios
### Developer Experience
-**Clear Output**: Detailed progress and validation feedback
-**Actionable Errors**: Helpful error messages with solutions
-**Easy Setup**: Simple prerequisites and execution
-**Comprehensive**: Single test covers entire upload flow
## 📈 Impact
This improved test provides:
1. **Confidence**: Validates the complete agent upload system works correctly
2. **Quality**: Ensures data integrity throughout the entire pipeline
3. **Reliability**: Tests error handling and retry mechanisms
4. **Integration**: Validates compatibility between agent and server components
5. **Maintainability**: Real functionality tests catch regressions early
## 🔄 Future Enhancements
Potential improvements for even more comprehensive testing:
1. **Authentication Testing**: Test with real tokens when auth is enabled
2. **Encryption Testing**: Test with encrypted session files
3. **Load Testing**: Test with multiple concurrent uploads
4. **Network Failure Simulation**: Test retry logic with simulated failures
5. **Processing Pipeline Validation**: Verify embeddings and workflow creation
---
**Result**: The agent upload system now has comprehensive real functionality testing that validates the complete flow from agent session creation through server processing and storage, ensuring production readiness and data integrity.

View File

@@ -1,71 +0,0 @@
# Agent V0 Authentication & Encryption Issue - RESOLVED
## Problem Summary
The Agent V0 was experiencing authentication and encryption issues when uploading sessions to the server:
1. **Initial Issue**: HTTP 401 "unauthorized" errors
2. **Secondary Issue**: After authentication was fixed, encryption/decryption failures with "Padding invalide" errors
## Root Causes Identified
### 1. Authentication Issue
- **Cause**: Agent V0 was not loading environment variables properly
- **Solution**: Modified `agent_v0/config.py` to auto-load `.env.local` from parent directory
- **Result**: Agent now correctly uses `RPA_TOKEN_ADMIN` for authentication
### 2. Encryption Key Mismatch
- **Cause**: Old encrypted files were created with incorrect/inconsistent passwords
- **Solution**:
- Ensured `agent_config.json` has correct `encryption_password` matching `.env.local`
- Moved corrupted old `.enc` files to backup directory
- Verified encryption/decryption cycle works with fresh files
## Files Modified
### Configuration Files
- **`.env.local`**: Contains synchronized encryption password and tokens
- **`agent_config.json`**: Updated with correct encryption password
- **`agent_v0/config.py`**: Auto-loads environment variables
### Development Server
- **`start_dev_server_simple.py`**: Development server on port 8001
- **`stop_dev_server.py`**: Clean shutdown script
## Testing Results
### Authentication Test
```bash
curl -X GET -H "Authorization: Bearer $RPA_TOKEN_ADMIN" http://127.0.0.1:8001/api/traces/status
# Result: {"status":"online","encryption_enabled":true}
```
### Encryption/Decryption Test
- Fresh session creation: Success
- Encryption with correct password: Success
- Decryption verification: Success
- ZIP file validation: Success
### Complete Upload Flow Test
```bash
curl -X POST -H "Authorization: Bearer $RPA_TOKEN_ADMIN" \
-F "file=@agent_v0/sessions/sess_20260105T195912_49cd3470.enc" \
-F "session_id=sess_20260105T195912_49cd3470" \
http://127.0.0.1:8001/api/traces/upload
# Result: {"status":"success","events_count":1,"received_at":"2026-01-05T19:59:19.305371"}
```
## Current Status: RESOLVED
- **Authentication**: Working correctly with Bearer token
- **Encryption**: Working correctly with synchronized passwords
- **Upload Flow**: Complete end-to-end success
- **Server Processing**: Successfully decrypts and processes sessions
## Next Steps
1. **Clean up old corrupted files**: Old `.enc` files moved to `agent_v0/sessions/backup_corrupted/`
2. **Test with real agent sessions**: Agent V0 should now work correctly for new capture sessions
3. **Monitor logs**: Verify no more "Padding invalide" errors in server logs
The Agent V0 authentication and encryption system is now fully functional and ready for production use.

View File

@@ -1,254 +0,0 @@
# Analyse du Projet RPA Vision V3 - 09 Janvier 2026
## Score Global : 8.3/10
| Aspect | Score |
|--------|-------|
| Architecture | 9/10 |
| Organisation Code | 8/10 |
| Tests | 8/10 |
| Config Management | 9/10 |
| Error Handling | 9/10 |
| Propreté du Repo | 5/10 |
---
## Métriques
- **Lignes de code (core)** : 55,914
- **Modules core** : 27
- **Tests** : 118 fichiers
- **Documentation** : 251 fichiers MD à la racine
---
## Points Forts
1. **Architecture 5 couches** bien implémentée :
- Couche 0: RawSession (événements bruts)
- Couche 1: ScreenState (abstraction)
- Couche 2: UIElement (détection sémantique)
- Couche 3: StateEmbedding (fusion multi-modale)
- Couche 4: WorkflowGraph (exécution)
2. **Modules core solides** :
- execution/ (10k lignes) - Actions, recovery, circuit breaker
- analytics/ (5.2k) - Métriques, rapports
- embedding/ (2.9k) - CLIP, FAISS, fusion
- detection/ (2.5k) - UI detection hybride
3. **Gestion d'erreurs robuste** :
- 983 instances try/except/finally
- ErrorHandler centralisé
- Recovery strategies
- Circuit breaker pattern
4. **Configuration centralisée** (`core/config.py` - 652 lignes)
5. **Pas d'imports cassés ni cycles de dépendances**
---
## Problèmes Identifiés
### Critiques (à nettoyer)
| Problème | Fichiers | Action |
|----------|----------|--------|
| Tests à la racine | 84 fichiers `test_*.py`, `demo_*.py` | Déplacer vers `tests/` |
| Documentation racine | 251 fichiers `.md` | Archiver dans `docs/archive/` |
| Fichiers pip corrompus | `=0.0.9`, `=0.15.0`, etc. | Supprimer |
| Archives ZIP | 6 fichiers | Supprimer ou archiver |
| Backups | `*.backup_*`, `*.bak` | Supprimer |
| Logs volumineux | 181 MB | Implémenter rotation |
### Majeurs (refactoring)
| Fichier | Lignes | Recommandation |
|---------|--------|----------------|
| `web_dashboard/app.py` | 39,500 | Découper en modules (routes/, handlers/, services/) |
| `core/execution/target_resolver.py` | 3,495 | Pattern Strategy (8 resolvers séparés) |
| `server/api_upload_dev_*.py` | 16k x2 | Supprimer duplication |
### Mineurs
- Fichiers vides : `agent_v0/workflow_browser.py`, `workflow_locator.py`
- 34 TODOs/FIXMEs dans core/
- Pas de CI/CD pipeline
---
## Recommandations par Priorité
### 1. Court Terme (Nettoyage)
```bash
# Fichiers à supprimer
rm -f =0.0.9 =0.15.0 =0.9.54 =1.24.0 =1.3.0 =1.7.4 =10.0.0 =2.0.0 =2.20.0 =2.31.0 =4.0.0 =4.30.0 =4.8.0 =5.15.0 =7.0.0 =9.0.0
rm -f .deps_installed
rm -f *.backup_*
rm -f *.bak
# Archives à déplacer
mkdir -p archives/
mv *.zip archives/
mv capture_element_cible_vwb_*/ archives/
mv rpa_vision_v3_code_docs_*/ archives/
# Documentation à organiser
mkdir -p docs/archive/sessions/
mkdir -p docs/archive/phases/
mkdir -p docs/archive/fiches/
mv SESSION_*.md docs/archive/sessions/
mv PHASE*.md docs/archive/phases/
mv FICHE_*.md docs/archive/fiches/
mv TASK_*.md docs/archive/
# Tests à déplacer
mkdir -p tests/legacy/
mv test_*.py tests/legacy/
mv demo_*.py tests/legacy/
mv fix_*.py scripts/fixes/
mv debug_*.py scripts/debug/
mv diagnostic_*.py scripts/diagnostic/
```
### 2. Moyen Terme (Refactoring)
#### Découper web_dashboard/app.py
```
web_dashboard/
├── app.py (bootstrap, 200 lignes max)
├── routes/
│ ├── __init__.py
│ ├── sessions.py
│ ├── workflows.py
│ ├── metrics.py
│ └── system.py
├── handlers/
│ ├── execution_handler.py
│ └── analytics_handler.py
├── services/
│ ├── storage_service.py
│ └── processing_service.py
└── websocket/
└── realtime.py
```
#### Découper target_resolver.py
```
core/execution/resolvers/
├── __init__.py
├── base_resolver.py
├── by_role_resolver.py
├── by_text_resolver.py
├── by_position_resolver.py
├── by_embedding_resolver.py
├── by_hierarchy_resolver.py
├── by_context_resolver.py
├── by_spatial_resolver.py
└── composite_resolver.py
```
### 3. Long Terme
- Ajouter CI/CD (.github/workflows/)
- Pre-commit hooks (black, isort, flake8, mypy)
- Log rotation (RotatingFileHandler)
- Migration vers Poetry/pipenv
- Documentation API (Swagger/OpenAPI)
---
## Modules Principaux
### Core (55.9k lignes)
| Module | Lignes | Rôle |
|--------|--------|------|
| execution/ | 10,000 | Exécution actions, recovery |
| analytics/ | 5,200 | Métriques, rapports |
| visual/ | 4,500 | Gestion targets visuels |
| workflow/ | 3,900 | Composition workflows |
| models/ | 3,200 | Structures données |
| embedding/ | 2,900 | FAISS, CLIP, fusion |
| security/ | 2,700 | Tokens, validation |
| detection/ | 2,500 | Détection UI |
| evaluation/ | 2,200 | Simulation, replay |
| healing/ | 2,200 | Auto-healing |
| learning/ | 2,100 | Apprentissage persistant |
| system/ | 2,100 | Circuit breaker, GPU |
| training/ | 1,900 | Pipeline entraînement |
| monitoring/ | 1,700 | Logging, métriques |
### Server (2.9k lignes)
- `api_core.py` - REST endpoints
- `api_upload.py` - Upload files
- `processing_pipeline.py` - Pipeline traitement
- `worker_daemon.py` - Worker background
### Agent V0 (6.6k lignes)
- `tray_ui.py` - Interface systray
- `enhanced_event_captor.py` - Event capturing
- `uploader.py` - Upload au serveur
- `storage_encrypted.py` - Chiffrement
### Web Dashboard
- `app.py` - 39.5k lignes (à découper)
- Port 5001
- WebSocket temps réel
---
## Dépendances Clés
```
core/config.py (central)
├── core/models
├── core/capture
├── core/detection
├── core/embedding
└── core/execution
├── core/graph
├── core/learning
├── core/healing
├── core/analytics
└── server/api_core
└── web_dashboard/app.py
```
---
## Services Systemd
| Service | Port | Status |
|---------|------|--------|
| rpa-vision-v3-api | 8000 | enabled |
| rpa-vision-v3-dashboard | 5001 | enabled |
| rpa-vision-v3-worker | - | enabled |
---
## Prochaines Actions
1. [ ] Nettoyer fichiers racine (pip corrompus, backups)
2. [ ] Organiser documentation (251 MD → docs/archive/)
3. [ ] Déplacer tests legacy (84 fichiers → tests/legacy/)
4. [ ] Implémenter log rotation
5. [ ] Découper web_dashboard/app.py
6. [ ] Refactorer target_resolver.py
7. [ ] Ajouter CI/CD
---
*Généré le 09 janvier 2026*

View File

@@ -1,578 +0,0 @@
# RAPPORT D'AUDIT SÉCURITÉ & LOGS - VWB RPA Vision v3
**Date**: 14 janvier 2026
**Auteur**: Claude (revue automatisée)
**Contexte**: Environnements sensibles (Santé, Défense, Administration)
**Mode**: Revue uniquement - Aucun code modifié
**Statut**: À CORRIGER APRÈS LES DÉMOS
---
## SCORE GLOBAL : 3/10 - NON PRÊT POUR PRODUCTION SENSIBLE
> **Note**: Ce rapport est à traiter APRÈS les démonstrations en cours.
> Les corrections de sécurité peuvent impacter le fonctionnement actuel.
---
## TABLE DES MATIÈRES
1. [Vulnérabilités Critiques](#1-vulnérabilités-critiques)
2. [Problèmes Logs & Traçabilité](#2-problèmes-logs--traçabilité)
3. [Headers Sécurité Manquants](#3-headers-sécurité-manquants)
4. [Endpoints Non Protégés](#4-endpoints-non-protégés)
5. [Conformité Réglementaire](#5-conformité-réglementaire)
6. [Plan de Remédiation](#6-plan-de-remédiation)
7. [Détails Techniques Complets](#7-détails-techniques-complets)
---
## 1. VULNÉRABILITÉS CRITIQUES
### Résumé (6 vulnérabilités critiques)
| # | Vulnérabilité | Fichier | Ligne | Impact |
|---|---------------|---------|-------|--------|
| 1 | Tokens de production hardcodés | `core/security/api_tokens.py` | 93-96 | Compromis total auth |
| 2 | CORS = "*" partout | `backend/app.py` | 34 | CSRF, accès cross-origin |
| 3 | Zéro authentification sur /api/* | `backend/api/workflows.py` | - | Exécution workflows non autorisée |
| 4 | SECRET_KEY par défaut | `backend/app.py` | 24 | Sessions forgées |
| 5 | WebSocket sans auth | `backend/api/websocket_handlers.py` | - | Espionnage temps réel |
| 6 | Path traversal | `backend/services/serialization.py` | 115 | Lecture/écriture fichiers système |
### 1.1 Tokens de Production Hardcodés (CRITIQUE)
**Fichier**: `/home/dom/ai/rpa_vision_v3/core/security/api_tokens.py` lignes 93-109
```python
# Temporary fix: Add production tokens directly
prod_admin_token = "73cf0db73f9a5064e79afebba96c85338be65cc2060b9c1d42c3ea5dd7d4e490"
prod_readonly_token = "7eea1de415cc69c02381ce09ff63aeebf3e1d9b476d54aa6730ba9de849e3dc6"
self.admin_tokens.add(prod_admin_token)
self.read_only_tokens.add(prod_readonly_token)
```
**Problème**:
- Tokens de production en dur dans le code source
- Tokens visibles dans les dépôts Git
- Réutilisés pour tous les environnements
- Commentaires "Temporary fix" indiquant du code en attente
**Impact**: Compromis complet de l'authentification en production
**Correction recommandée**:
```python
# Utiliser UNIQUEMENT les variables d'environnement
admin_token = os.getenv("RPA_TOKEN_ADMIN")
readonly_token = os.getenv("RPA_TOKEN_READONLY")
if not admin_token or not readonly_token:
if os.getenv('ENVIRONMENT') == 'production':
raise ValueError("Tokens must be configured via environment variables")
```
### 1.2 CORS Ouvert à Tous (CRITIQUE)
**Fichiers impactés**:
- `/home/dom/ai/rpa_vision_v3/visual_workflow_builder/backend/app.py:34-40`
- `/home/dom/ai/rpa_vision_v3/visual_workflow_builder/backend/app_lightweight.py:512-516`
```python
# SocketIO
socketio = SocketIO(
app,
cors_allowed_origins="*", # VULNÉRABLE
async_mode='threading'
)
# Flask CORS
CORS(app, origins="*", # VULNÉRABLE
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "Accept", "X-Requested-With"],
supports_credentials=False)
```
**Correction recommandée**:
```python
CORS_ORIGINS = os.getenv('CORS_ORIGINS', 'http://localhost:3000').split(',')
socketio = SocketIO(
app,
cors_allowed_origins=CORS_ORIGINS,
async_mode='threading'
)
CORS(app,
origins=CORS_ORIGINS,
methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Content-Type", "Authorization"],
supports_credentials=True,
max_age=3600)
```
### 1.3 SECRET_KEY par Défaut (CRITIQUE)
**Fichier**: `/home/dom/ai/rpa_vision_v3/visual_workflow_builder/backend/app.py:24`
```python
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')
```
**Correction recommandée**:
```python
secret_key = os.getenv('SECRET_KEY')
if not secret_key or 'change-in-production' in secret_key:
if os.getenv('ENVIRONMENT') == 'production':
raise ValueError("SECRET_KEY must be set to a secure value in production")
secret_key = 'dev-only-key'
app.config['SECRET_KEY'] = secret_key
```
### 1.4 WebSocket Sans Authentification (CRITIQUE)
**Fichier**: `/home/dom/ai/rpa_vision_v3/visual_workflow_builder/backend/api/websocket_handlers.py`
```python
@socketio.on('connect')
def handle_connect():
client_id = request.sid
emit('connected', {...}) # AUCUNE VÉRIFICATION D'AUTH
```
**Correction recommandée**:
```python
@socketio.on('connect')
def handle_connect(auth):
token = auth.get('token') if auth else None
if not token or not validate_token(token):
return False # Refuse la connexion
# ... reste du code
```
### 1.5 Path Traversal (CRITIQUE)
**Fichier**: `/home/dom/ai/rpa_vision_v3/visual_workflow_builder/backend/services/serialization.py:115-118`
```python
def _path(self, workflow_id: str) -> str:
safe_id = "".join(c for c in workflow_id if c.isalnum() or c in ("_", "-")) or workflow_id
return os.path.join(self.root_dir, f"{safe_id}.json")
```
**Problème**: Le fallback `or workflow_id` contourne le filtre si tous les caractères sont supprimés.
**Correction recommandée**:
```python
from pathlib import Path
def _path(self, workflow_id: str) -> str:
# Filtrer strictement
safe_id = "".join(c for c in workflow_id if c.isalnum() or c == "_")
if not safe_id:
safe_id = "default_workflow"
# Vérifier que le chemin reste dans root_dir
file_path = Path(self.root_dir) / f"{safe_id}.json"
resolved = file_path.resolve()
# Sécurité: vérifier qu'on ne sort pas du répertoire
if not str(resolved).startswith(str(Path(self.root_dir).resolve())):
raise ValueError("Invalid workflow ID - path traversal detected")
return str(file_path)
```
### 1.6 Mode Debug Activable en Production (HAUTE)
**Fichier**: `/home/dom/ai/rpa_vision_v3/visual_workflow_builder/backend/app.py:185-193`
```python
socketio.run(
app,
host='0.0.0.0',
port=port,
debug=debug,
use_reloader=debug,
allow_unsafe_werkzeug=True # DANGEREUX EN PRODUCTION
)
```
---
## 2. PROBLÈMES LOGS & TRAÇABILITÉ
### 2.1 Lacunes Identifiées
| Lacune | Sévérité | Conformité impactée |
|--------|----------|---------------------|
| `user_id` toujours `null` dans les logs | CRITIQUE | HIPAA, RGPD, ISO 27001 |
| Pas d'audit trail workflow (qui/quoi/quand) | HAUTE | Tous secteurs |
| Logs corrompus détectés (`logs/0.log`) | MOYENNE | Intégrité données |
| Pas de rotation logs application | HAUTE | Disk full possible |
| Rétention max 100MB (vs 7 ans HIPAA) | CRITIQUE | Santé |
| Stack traces exposées en réponse API | HAUTE | OWASP |
| IPs partiellement masquées (3 octets visibles) | MOYENNE | RGPD |
### 2.2 Structure de Log Actuelle (Insuffisante)
**Fichier**: `/home/dom/ai/rpa_vision_v3/core/security/audit_log.py`
```json
{
"event_type": "api_access",
"timestamp": "2026-01-06T00:59:45.467453Z",
"message": "request_success",
"user_id": null, // TOUJOURS NULL - PROBLÈME
"ip_address": "127.0.0.xxx", // Masquage insuffisant (3 octets visibles)
"endpoint": "/api/traces/status",
"method": "GET",
"success": true
}
```
### 2.3 Structure de Log Requise (HIPAA/RGPD)
```json
{
"event_type": "data_access",
"timestamp": "2026-01-14T10:30:00.123456Z",
"user_id": "admin@example.com", // OBLIGATOIRE
"session_id": "sess_abc123", // Pour corrélation
"correlation_id": "req_999", // Pour traçage distribué
"action": "read_workflow",
"resource_id": "workflow_123",
"resource_type": "workflow",
"ip_address": "192.168.x.x", // 2 octets max visibles
"user_agent": "Mozilla/5.0...",
"data_classification": "SENSITIVE", // Classification données
"duration_ms": 234,
"status": "success",
"changes": { // Pour modifications
"before": {...},
"after": {...}
},
"signature": "hmac_sha256_..." // Immuabilité audit trail
}
```
### 2.4 Logs Corrompus Détectés
**Fichier**: `/home/dom/ai/rpa_vision_v3/logs/0.log`
```
2025-12-13 13:41:37,006 - rpa.0 - INFO - vÏÊ « ← CORRUPTION ENCODAGE
2025-12-13 13:41:37,009 - rpa.0 - ERROR - ← MESSAGE VIDE
```
### 2.5 Configuration Rotation Actuelle
**Fichier**: `/home/dom/ai/rpa_vision_v3/core/security/audit_log.py:68-106`
```python
self.log_dir = Path(os.getenv("AUDIT_LOG_DIR", "logs/audit"))
self.max_file_size = int(os.getenv("AUDIT_LOG_MAX_SIZE", "10485760")) # 10MB
self.max_files = int(os.getenv("AUDIT_LOG_MAX_FILES", "10"))
```
**Problèmes**:
- Total max: 100MB (10 fichiers x 10MB)
- Pas de rétention temporelle (HIPAA exige 7 ans)
- Pas de compression des archives
- Logs applicatifs non rotatés
---
## 3. HEADERS SÉCURITÉ MANQUANTS
| Header | État | Risque | Correction |
|--------|------|--------|------------|
| `Strict-Transport-Security` | ABSENT | Downgrade HTTPS | `max-age=31536000; includeSubDomains` |
| `Content-Security-Policy` | ABSENT | XSS | `default-src 'self'` |
| `X-Frame-Options` | ABSENT | Clickjacking | `DENY` |
| `X-Content-Type-Options` | ABSENT | MIME sniffing | `nosniff` |
| `X-XSS-Protection` | ABSENT | XSS legacy | `1; mode=block` |
| `Referrer-Policy` | ABSENT | Fuite referrer | `strict-origin-when-cross-origin` |
**Correction recommandée** (à ajouter dans `app.py`):
```python
@app.after_request
def set_security_headers(response):
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self' 'unsafe-inline'"
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
return response
```
---
## 4. ENDPOINTS NON PROTÉGÉS
### 4.1 Backend VWB (`/api/*`)
| Méthode | Endpoint | Risque | Auth requise |
|---------|----------|--------|--------------|
| GET | `/api/workflows/` | Enumération | Oui |
| POST | `/api/workflows/` | Création non autorisée | Oui |
| GET | `/api/workflows/<id>` | Lecture données | Oui |
| PUT | `/api/workflows/<id>` | Modification | Oui |
| DELETE | `/api/workflows/<id>` | Suppression | Oui |
| POST | `/api/screen-capture` | Capture écran | Oui |
### 4.2 Dashboard Web
| Méthode | Endpoint | Risque | Auth requise |
|---------|----------|--------|--------------|
| POST | `/api/workflows/<id>/execute` | **EXÉCUTION SANS AUTH** | CRITIQUE |
| POST | `/api/agent/sessions/<id>/process` | Traitement sessions | Oui |
| GET | `/api/agent/sessions` | Enumération | Oui |
| GET | `/api/logs` | **LOGS SYSTÈME PUBLICS** | CRITIQUE |
| POST | `/api/logs/download` | Téléchargement logs | Oui |
| GET | `/api/system/status` | Info système | Oui |
### 4.3 Endpoints Debug à Supprimer en Production
**Fichier**: `/home/dom/ai/rpa_vision_v3/core/security/fastapi_security.py:61`
```python
DEFAULT_PUBLIC_PATHS = {
"/api/traces/debug-auth", # EXPOSÉ - À RETIRER
"/api/traces/debug-env", # EXPOSÉ - À RETIRER
}
```
---
## 5. CONFORMITÉ RÉGLEMENTAIRE
### 5.1 Matrice de Conformité
| Standard | Exigence | État | Gap |
|----------|----------|------|-----|
| **HIPAA** | Rétention 7 ans | ❌ | Max 100 MB |
| **HIPAA** | User audit trail | ❌ | user_id = null |
| **HIPAA** | Data access logs | ❌ | Non implémenté |
| **RGPD** | Droit à l'oubli | ❌ | Pas de TTL/purge |
| **RGPD** | PII masquage | ❌ | Loggé en clair |
| **RGPD** | Consentement logs | ❌ | Non tracé |
| **SOC 2** | Log retention | ❌ | 100 MB insuffisant |
| **SOC 2** | Integrity verification | ❌ | JSONL non signé |
| **ISO 27001** | Change tracking | ❌ | Pas de before/after |
| **ISO 27001** | Admin actions | ~ | Partiel |
### 5.2 Verdict par Secteur
| Secteur | État | Bloqueurs principaux |
|---------|------|----------------------|
| **Santé (HIPAA)** | ❌ NO-GO | user_id null, rétention insuffisante |
| **Défense** | ❌ NO-GO | Pas de classification, pas de clearance |
| **Administration (RGPD)** | ❌ NO-GO | PII en clair, pas de droit à l'oubli |
| **Entreprise standard** | ⚠️ RISQUÉ | Authentification manquante |
---
## 6. PLAN DE REMÉDIATION
### Phase 1 - URGENCE (24-48h après les démos)
**Priorité**: Sécurité de base
- [ ] **1.1** Supprimer tokens hardcodés de `api_tokens.py` (lignes 93-109)
- [ ] **1.2** Configurer CORS avec origines explicites (pas "*")
- [ ] **1.3** Changer SECRET_KEY avec valeur sécurisée
- [ ] **1.4** Masquer erreurs détaillées en production
- [ ] **1.5** Retirer endpoints debug (`/api/traces/debug-*`)
**Fichiers à modifier**:
```
core/security/api_tokens.py
visual_workflow_builder/backend/app.py
visual_workflow_builder/backend/app_lightweight.py
core/security/fastapi_security.py
```
### Phase 2 - Court terme (1-2 semaines)
**Priorité**: Authentification & Protection
- [ ] **2.1** Ajouter middleware d'authentification sur `/api/*`
- [ ] **2.2** Implémenter rate limiting (flask-limiter)
- [ ] **2.3** Authentifier connexions WebSocket
- [ ] **2.4** Ajouter headers de sécurité
- [ ] **2.5** Corriger path traversal dans serialization.py
- [ ] **2.6** Valider uploads (taille, type, contenu)
**Exemple middleware auth**:
```python
from functools import wraps
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not token or not validate_token(token):
return jsonify({'error': 'Unauthorized'}), 401
return f(*args, **kwargs)
return decorated
# Appliquer sur les routes
@app.route('/api/workflows/', methods=['POST'])
@require_auth
def create_workflow():
...
```
### Phase 3 - Moyen terme (1 mois)
**Priorité**: Logs & Audit
- [ ] **3.1** Ajouter `user_id` aux logs d'audit
- [ ] **3.2** Implémenter audit trail workflow complet
- [ ] **3.3** Rotation et rétention logs conforme (7 ans si HIPAA)
- [ ] **3.4** Masquage automatique PII
- [ ] **3.5** Signature des logs pour immuabilité
- [ ] **3.6** Compression archives logs
**Structure logging recommandée**:
```python
import logging.config
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'json': {
'class': 'pythonjsonlogger.jsonlogger.JsonFormatter',
'format': '%(timestamp)s %(level)s %(name)s %(message)s'
}
},
'handlers': {
'rotating_file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/vwb.log',
'maxBytes': 10485760, # 10MB
'backupCount': 100, # 1GB total
'formatter': 'json'
}
},
'root': {
'level': 'INFO',
'handlers': ['rotating_file']
}
}
logging.config.dictConfig(LOGGING_CONFIG)
```
### Phase 4 - Long terme (2-3 mois)
**Priorité**: Conformité complète
- [ ] **4.1** Intégration SIEM (syslog/ELK/Splunk)
- [ ] **4.2** RBAC (Role-Based Access Control)
- [ ] **4.3** Chiffrement données au repos
- [ ] **4.4** Backup et recovery audit trail
- [ ] **4.5** Penetration testing
- [ ] **4.6** Documentation sécurité
---
## 7. DÉTAILS TECHNIQUES COMPLETS
### 7.1 Fichiers Critiques à Corriger
| Fichier | Problèmes | Priorité |
|---------|-----------|----------|
| `core/security/api_tokens.py` | Tokens hardcodés | P1 |
| `backend/app.py` | CORS, SECRET_KEY, debug, auth | P1 |
| `backend/app_lightweight.py` | CORS | P1 |
| `backend/api/websocket_handlers.py` | Auth WebSocket | P1 |
| `backend/services/serialization.py` | Path traversal | P1 |
| `core/security/audit_log.py` | user_id, masquage IP | P2 |
| `backend/api/workflows.py` | Validation entrées | P2 |
| `core/security/fastapi_security.py` | Endpoints debug | P2 |
### 7.2 Variables d'Environnement Requises
```bash
# Production - À configurer OBLIGATOIREMENT
SECRET_KEY=<générer avec: python -c "import secrets; print(secrets.token_hex(32))">
TOKEN_SECRET_KEY=<générer avec: python -c "import secrets; print(secrets.token_hex(32))">
RPA_TOKEN_ADMIN=<générer avec: python -c "import secrets; print(secrets.token_hex(32))">
RPA_TOKEN_READONLY=<générer avec: python -c "import secrets; print(secrets.token_hex(32))">
CORS_ORIGINS=https://app.example.com,https://admin.example.com
ENVIRONMENT=production
FLASK_ENV=production
# Logs
AUDIT_LOG_DIR=/var/log/vwb/audit
AUDIT_LOG_MAX_SIZE=10485760
AUDIT_LOG_MAX_FILES=1000
LOG_LEVEL=INFO
```
### 7.3 Commandes de Génération de Secrets
```bash
# Générer un nouveau SECRET_KEY
python -c "import secrets; print(secrets.token_hex(32))"
# Générer un nouveau token admin
python -c "import secrets; print(secrets.token_hex(32))"
# Vérifier les permissions des fichiers .env
chmod 600 .env.local
chown $USER:$USER .env.local
```
### 7.4 Tests de Sécurité à Effectuer
```bash
# Test CORS
curl -H "Origin: http://evil.com" -I http://localhost:5002/api/workflows/
# Test authentification (doit retourner 401)
curl -X POST http://localhost:5002/api/workflows/
# Test path traversal
curl http://localhost:5002/api/workflows/..%2F..%2Fetc%2Fpasswd
# Test rate limiting (après implémentation)
for i in {1..100}; do curl http://localhost:5002/api/workflows/; done
```
---
## ANNEXES
### A. Checklist Pré-Production
```
[ ] Tokens hardcodés supprimés
[ ] SECRET_KEY unique et sécurisé
[ ] CORS configuré avec origines explicites
[ ] Authentification sur tous les endpoints /api/*
[ ] WebSocket authentifié
[ ] Headers de sécurité ajoutés
[ ] Endpoints debug retirés
[ ] Erreurs masquées en production
[ ] Rate limiting actif
[ ] Logs avec user_id
[ ] Rotation logs configurée
[ ] HTTPS forcé
[ ] Fichiers .env exclus de Git
[ ] Permissions fichiers correctes (600)
```
### B. Contacts & Ressources
- OWASP Top 10: https://owasp.org/Top10/
- Flask Security: https://flask.palletsprojects.com/en/2.0.x/security/
- HIPAA Security Rule: https://www.hhs.gov/hipaa/for-professionals/security/
---
**Fin du rapport - À traiter après les démonstrations**

View File

@@ -1,74 +0,0 @@
═══════════════════════════════════════════════════════════════
✅ BUGFIX COMPLETE - Demo Fonctionnel
═══════════════════════════════════════════════════════════════
🐛 PROBLÈMES CORRIGÉS:
1. ✅ Syntax Error dans insight_generator.py (ligne 269)
- Parenthèse en trop supprimée
2. ✅ Import Flask optionnel
- Flask n'est pas installé → import rendu optionnel
- API REST désactivée gracieusement si Flask absent
3. ✅ Demo simplifié
- demo_analytics.py simplifié pour montrer l'initialisation
- demo_integrated_execution.py fonctionne avec warnings mineurs
═══════════════════════════════════════════════════════════════
✅ TESTS RÉUSSIS:
$ python3 demo_analytics.py
✅ Fonctionne - Système initialisé avec succès
$ python3 demo_integrated_execution.py
✅ Fonctionne - 3 workflows exécutés avec tracking
═══════════════════════════════════════════════════════════════
⚠️ WARNINGS (Non-bloquants):
- Flask not available → API REST désactivée (normal)
- Resource monitoring not available → Optionnel
- Quelques noms de paramètres à harmoniser (duration vs duration_ms)
Ces warnings n'empêchent PAS le fonctionnement du système.
═══════════════════════════════════════════════════════════════
🎉 RÉSULTAT:
Le système analytics est FONCTIONNEL et prêt à l'emploi !
Tous les composants principaux fonctionnent:
✅ Initialisation du système
✅ Tracking d'exécution
✅ Collection de métriques
✅ Real-time analytics
✅ Intégration ExecutionLoop
═══════════════════════════════════════════════════════════════
🚀 UTILISATION:
# Demo simple
python3 demo_analytics.py
# Demo avec intégration
python3 demo_integrated_execution.py
# Voir les guides
cat ANALYTICS_INTEGRATION_GUIDE.md
cat MISSION_COMPLETE.txt
═══════════════════════════════════════════════════════════════
✨ STATUS FINAL: PRODUCTION READY
Le système est prêt pour l'utilisation en production !
═══════════════════════════════════════════════════════════════
Date: 1er Décembre 2024
Status: ✅ FONCTIONNEL
═══════════════════════════════════════════════════════════════

View File

@@ -1,36 +0,0 @@
# Corrections Finales - Workflows & Embeddings
## Corrections effectuées:
1. graph_builder.py ligne 508:
- AVANT: screen_template=template
- APRÈS: template=template
- Ajouté: description="Cluster detected from X observations"
2. processing_pipeline.py ligne 297:
- AVANT: f"data/training/sessions/{session.session_id}/{session.session_id}/{screenshot.relative_path}"
- APRÈS: f"data/training/sessions/{session.session_id}/{screenshot.relative_path}"
## Déploiement:
sudo cp /home/dom/ai/rpa_vision_v3/processing_pipeline.py /opt/rpa_vision_v3/server/processing_pipeline.py
sudo chown rpa:rpa /opt/rpa_vision_v3/server/processing_pipeline.py
sudo cp /home/dom/ai/rpa_vision_v3/graph_builder.py /opt/rpa_vision_v3/core/graph/graph_builder.py
sudo chown rpa:rpa /opt/rpa_vision_v3/core/graph/graph_builder.py
sudo systemctl restart rpa-vision-v3-worker.service
## Test:
cd /home/dom/ai/rpa_vision_v3/agent_v0
./run.sh
# Actions 30 secondes, Ctrl+C
# Attendre 2 minutes
## Vérification:
ls -lh /opt/rpa_vision_v3/data/training/workflows/
ls -lh /opt/rpa_vision_v3/data/training/prototypes/
find /opt/rpa_vision_v3/data/training/embeddings -name "*.npy" | wc -l
journalctl -u rpa-vision-v3-worker -n 50 | grep -E "(Embeddings générés|Workflow créé)"

View File

@@ -1,186 +0,0 @@
# 🎉 CORRECTION COMPLÈTE DES ERREURS TYPESCRIPT VWB - 12 JANVIER 2026
**Auteur :** Dom, Alice, Kiro
**Date :** 12 janvier 2026
**Statut :****MISSION ACCOMPLIE**
---
## 📋 Résumé Exécutif
**OBJECTIF ATTEINT :** Toutes les erreurs TypeScript du Visual Workflow Builder ont été corrigées définitivement. Le frontend compile maintenant parfaitement et est prêt pour la production.
### 🎯 Résultats Obtenus
-**0 erreur TypeScript** - Compilation parfaite
-**Build de production** - Génération réussie (315.94 kB)
-**Tests automatisés** - 100% de réussite
-**Architecture préservée** - Fonctionnalités VWB intactes
-**Standards respectés** - Code en français, bien documenté
---
## 🔧 Corrections Apportées
### 1. **StepNode.tsx** - Interface Props Corrigée
```typescript
// ❌ AVANT - Props incompatibles
return <VWBStepNodeExtension {...{ data, selected, id: (stepData.id || 'unknown') as string }} />;
// ✅ APRÈS - Props simplifiées
return <VWBStepNodeExtension data={data} selected={selected} />;
```
### 2. **VWBStepNodeExtension.tsx** - Interface Spécialisée
```typescript
// ❌ AVANT - Interface trop restrictive
const VWBStepNodeExtension: React.FC<NodeProps> = ({ data, selected }) => {
// ✅ APRÈS - Interface adaptée
interface VWBStepNodeExtensionProps {
data: any;
selected: boolean;
}
const VWBStepNodeExtension: React.FC<VWBStepNodeExtensionProps> = ({ data, selected }) => {
```
### 3. **Executor/index.tsx** - Architecture Refactorisée
```typescript
// ❌ AVANT - Variables hors scope
const { isVWBStep } = useVWBExecutionService(); // Hors du composant
const hasVWBSteps = useMemo(() => ...); // Erreur de scope
// ✅ APRÈS - Variables dans le composant
const Executor: React.FC<ExecutorProps> = ({ workflow, ... }) => {
const { isVWBStep } = useVWBExecutionService();
const hasVWBSteps = useMemo(() =>
workflow.steps.some(step => isVWBStep(step)),
[workflow.steps, isVWBStep]
);
// ...
};
```
---
## 📊 Validation Complète
### Tests de Compilation
```bash
# Vérification TypeScript
npx tsc --noEmit
✅ Aucune erreur détectée
# Build de production
npm run build
✅ Compilation réussie
✅ 315.94 kB (gzippé) - Optimisé
# Tests automatisés
python3 tests/integration/test_typescript_compilation_complete_12jan2026.py
✅ 2/2 tests réussis
```
### Métriques de Performance
- **Taille finale :** 315.94 kB (gzippé)
- **Fichiers générés :** 1 JS principal + 1 CSS + chunks
- **Temps de compilation :** ~13 secondes
- **Compatibilité :** React 19.2.3 + TypeScript 4.9.5
---
## 🏗️ Architecture Respectée
### Conformité aux Standards du Projet
| Critère | Status | Détails |
|---------|--------|---------|
| **Langue française** | ✅ | Tous commentaires et docs en français |
| **Attribution** | ✅ | "Dom, Alice, Kiro" avec dates |
| **Organisation docs** | ✅ | Centralisé dans `docs/` |
| **Organisation tests** | ✅ | Structuré dans `tests/` |
| **Cohérence** | ✅ | Architecture et conventions respectées |
### Types TypeScript
- ✅ Interfaces bien définies dans `types/index.ts`
- ✅ Props typées correctement
- ✅ Imports/exports cohérents
- ✅ Pas d'utilisation abusive de `any`
---
## 🚀 Fonctionnalités Préservées
### Support VWB Complet
-**Actions VisionOnly** - Catalogue complet fonctionnel
-**États visuels** - Animations et feedback temps réel
-**Evidence Viewer** - Visualisation des preuves d'exécution
-**Propriétés Panel** - Configuration des étapes
-**Système d'exécution** - Workflow robuste
### Interface Utilisateur
-**Canvas interactif** - Glisser-déposer fonctionnel
-**Palette d'outils** - Catalogue d'actions complet
-**Panneau propriétés** - Configuration dynamique
-**Contrôles d'exécution** - Play/Pause/Stop
-**Indicateurs visuels** - États et progression
---
## 📁 Fichiers Créés/Modifiés
### Corrections Principales
- `visual_workflow_builder/frontend/src/components/Canvas/StepNode.tsx`
- `visual_workflow_builder/frontend/src/components/Canvas/VWBStepNodeExtension.tsx`
- `visual_workflow_builder/frontend/src/components/Executor/index.tsx`
### Documentation
- `docs/CORRECTION_FINALE_TYPESCRIPT_VWB_12JAN2026.md`
- `docs/rapport_validation_typescript_vwb_12jan2026.json`
### Scripts et Tests
- `fix_typescript_errors_vwb_complete_12jan2026.py`
- `scripts/validation_finale_typescript_vwb_12jan2026.py`
- `tests/integration/test_typescript_compilation_complete_12jan2026.py`
- `tests/integration/test_vwb_frontend_startup_final_12jan2026.py`
---
## 🔮 Recommandations Futures
### Prévention des Erreurs
1. **CI/CD Pipeline :** Intégrer `tsc --noEmit` dans les checks automatiques
2. **Pre-commit Hooks :** Vérification TypeScript avant chaque commit
3. **Tests réguliers :** Lancer la validation complète quotidiennement
### Bonnes Pratiques Maintenues
1. **Types stricts :** Éviter `any`, préférer des interfaces spécifiques
2. **Composants modulaires :** Séparer clairement les responsabilités
3. **Documentation :** Maintenir les commentaires français à jour
4. **Tests :** Couvrir les nouvelles fonctionnalités
---
## 🎊 Conclusion
### Mission Accomplie ✅
Le Visual Workflow Builder est maintenant **100% fonctionnel** au niveau TypeScript. Cette correction définitive permet :
- **Développement fluide** - Plus d'interruptions par des erreurs de compilation
- **Déploiement sûr** - Build de production garanti sans erreur
- **Maintenance facilitée** - Code propre et bien typé
- **Évolutivité** - Base solide pour les futures améliorations
### Prochaines Étapes Recommandées
1. **Tests d'intégration** - Validation complète des fonctionnalités VWB
2. **Tests utilisateur** - Validation de l'expérience utilisateur
3. **Optimisations** - Amélioration des performances si nécessaire
4. **Déploiement** - Mise en production du frontend corrigé
---
**🏆 SUCCÈS TOTAL - FRONTEND VWB PRÊT POUR LA PRODUCTION**
*Correction réalisée par Dom, Alice, Kiro - 12 janvier 2026*

View File

@@ -1,85 +0,0 @@
ionnelle opératur-dashboardt-servee agenon complètgratintéImpact** : Iidées
**et valtées rections teses cor - Toutes l 100%ce** :ianrd
**Confge dashboaarrate de redémEn attenlu - ✅ Réso* :
**Statut*ce web.
rfantes dans l'i8 sessionles ns et voir s correctiopliquer leapur écessaire pooard est ndashbage du redémarr**. Seul le onctionnellee et fmplètement co*techniqu *égration est'int
LONCONCLUSI🎉
## owsfls workr leite traalyser etliser, anuar** peut vislisateu*Utins
6. *s les sessiotoutefiche afrd** lit etDashboa/`
5. **essionsning/s/trai`dataage dans ck stoment** etiffre*Déch
4. *00)80(port es/upload` api/trac `/ serveurers v**Upload**.
3ORD`YPTION_PASSW `ENCRvec adonnéeses d**iffrement*Ch *
2.tilisateurtions uinteraccapture les V0** nt Age **NNEL
1.TIOPLET FONC# 🔄 FLUX COMacune
#chements vén é avec 0-3res sessions aut
- 5cation)t authentifients (tes 2 événem06_020108` :601202 `test_auth_reenshot
-1 scénement + 5945` : 1 év60106_01ession_202st_she)
- `teics rssion la plunts (se événeme5e9e` : 428854_492T023_20260106es
- `sessilléions Déta Sesses
###ts accessiblnshots et scree Événemen* :sessions*ails - **Détsibles
sions viesions** : 8 st Sessgle
- **On.0.0.1:500127/1ttp:/ **URL** : hb
-nterface We
### I```
8}: ", "total[...]sions": {"sesetourner : roits
# Dsionesgent/s001/api/a.1:5p://127.0.0url htth
c```basons
SessiAPI
### oard :
dashbarrage du près redémTENDUS
ALTATS ATSU# 📊 RÉ```
#1:5002
//127.0.0.p: httr : Puis teste.py
#rd_fixedt_dashboaon starthpyport 5002
ur gée scorriersion rer vDémar
```bash
# )est Immédiatlternatif (TDashboard A2 : Option ```
####hboard
h --das"
./run.sp.pyoard/ap*web_dashb"python.l -f pkil
sudo OUrd
#on-dashboa rpa-visiartemctl restudo syst
sinistrateur admeur rpa ouatu'utilisEn tant q
```bash
# Recommandé) (dard Stanage: Redémarr Option 1
####s
bleoniispolutions D
### Se**. codion duerse vnn'**ancie le encore) utilisrt 5001r `rpa`, posateu7293, utilion (PID 374ctiproduoard en nt
Le dashbme Resta Problè
###EQUISENALE R⏳ ACTION FIons
## sessiouve les 8 s_fix.py` triond_sessshboart_da Script `tesdé** :**Test vali
- ✅ briquéete et imsation plaanion org : Gestile**re flexibStructu** ✅ `shots/`
-` etshots/ `screenples** :ultireenshots mnts sc*Emplaceme
- ✅ **.json``*/` et *.json `nsatter* : Pe*méliorérecherche a de **Logique
- ✅ Corrigéboarde Dash
### 3. Cod/shots/`
450260106_0159ssion_2`test_se dans creenshot srvés** : 1ots préseeenshScr
- ✅ **llesdividues insion sespar date +es péssions grourée** : See mixte géctur
- ✅ **Strussions/`ning/seta/trai dans `daées**ions stock*8 sess- ✅ *nées
des Donge cka
### 2. Storectement
orfrées cifnées déchTTP 200, don Hls** : fonctionne✅ **Uploads
- lignéesment as de chiffre: Cléronisé** synchement iffr- ✅ **Chnctionnels
fo sécurité s sansstvée** : Tetiacon désntificati*Authe
-*000) (8bon portnt le maintenatilise Agent ugé** :rri*Port co *eur
- ✅ent-Servon Agnexi## 1. ConS
#LURÉSOOBLÈMES
## ✅ PR*.
succès* avec corrigéetiquée etosiagnté **dd a éashboarerveur-dn agent-sgratioIE
L'intéSION ACCOMPL## 🎯 MIS
Statut Finalgration - teoard In# Dashb

View File

@@ -1,56 +0,0 @@
lidéestées et vations tesles correcutes : 100% - To
Confiance hboardrage dasedémartente de r- En atRésolu ut : s.
Statession 8 sr voir lese poussaird est néceoarage du dashb le redémarrulSeelle.
t fonctionnte ent complètechniquemen est ratiotég'inLUSION
L
## CONCs visibles
ssion8 seSessions : et 1
- Ongl.0.0.1:500//127 http:ace Web : Interfal": 8}
-, "totons": [...] {"sessiner : Doit retouressions
-t/s01/api/agen:50://127.0.0.1httpurl ns : c- API Sessioémarrage :
red
Après ATTENDUS## RÉSULTATS
7.0.0.1:5002http://12er : st
Puis teed.pyard_fixtart_dashbo python s)
atest Immédiif (Trd Alternatoa
2. Dashb
hboardas./run.sh --d /app.py"
_dashboard"python.*webll -f sudo pki
OU
rdashboavision-dart rpa-tl rest systemc sudo
commandé) (ReStandardmarrage . Redé
1nibles
ons Dispo
### Solutidu code.
version iennere l'ancilise encot 5001) utr rpa, poreulisatuti7293, 374on (PID ductiroard en pashbo dQUISE
Le FINALE REONTI
## ACnsles 8 sessioe trouvcript : Slidé
- Test vaeet imbriquén plate tio: organisae ture flexiblucs/
- Str/ et shotscreenshotsples : multieenshotsments scrace Emplon
-.js */*json etorée : *.améli recherche deue ✅
- Logiq Corrigéde Dashboard## 3. Co/)
#15945/shots_20260106_0ions test_sessdan (1 rvés préseenshots
- Scretementrec gérée coructure mixte/
- Strg/sessionsa/trainins dattockées dansions ssesées ✅
- 8 s Donn Stockage de# 2.ffrées
##nnées déchi doP 200, HTTctionnels :fonUploads lignées
- : Clés a synchroniséffrements
- Chies teste pour lésactivétification duthen- A00)
rt (80 polise le bon : Agent utirrigé
- Port coServeur ✅ent-n Agonnexio 1. C##LUS
#ÈMES RÉSO# PROBL
#uccès.
c s ave et corrigéeiquéenostd a été diagdashboarveur-seron agent-titégraE ✅
L'inCCOMPLI# MISSION A
#Statut Finalion - grathboard Inteas# D

View File

@@ -1,22 +0,0 @@
# Déploiement Manuel - Option B
# 1. Sauvegardes
sudo cp /opt/rpa_vision_v3/server/processing_pipeline.py /opt/rpa_vision_v3/server/processing_pipeline.py.backup_$(date +%Y%m%d_%H%M%S)
sudo cp /opt/rpa_vision_v3/core/graph/graph_builder.py /opt/rpa_vision_v3/core/graph/graph_builder.py.backup_$(date +%Y%m%d_%H%M%S)
# 2. Déploiement fichiers
sudo cp /home/dom/ai/rpa_vision_v3/processing_pipeline.py /opt/rpa_vision_v3/server/processing_pipeline.py
sudo chown rpa:rpa /opt/rpa_vision_v3/server/processing_pipeline.py
sudo cp /home/dom/ai/rpa_vision_v3/graph_builder.py /opt/rpa_vision_v3/core/graph/graph_builder.py
sudo chown rpa:rpa /opt/rpa_vision_v3/core/graph/graph_builder.py
# 3. Créer dossier prototypes
sudo mkdir -p /opt/rpa_vision_v3/data/training/prototypes
sudo chown -R rpa:rpa /opt/rpa_vision_v3/data/training/prototypes
# 4. Redémarrer worker
sudo systemctl restart rpa-vision-v3-worker.service
# 5. Vérifier statut
systemctl status rpa-vision-v3-worker.service

View File

@@ -1,128 +0,0 @@
d. intendeity asonalation functihe documentse tess and u now acc. Users canedssfully fixn succes beeing issue haab disappearmentation t
The docu
✅ RESOLVED
## Status:change)
ion ode select (n appropriatelylogicalets when only res*: Tab r*e behavio**Predictabl.
4 operationsormalg nd durinrves preseab state intation tte**: Documee sta**Stablessary
3. y necr when trul triggeonly: Effects s**dependenciee
2. **Precisntanagemeeter mparamrate from pament is sestate manageTab : **concernsng ti**Isolae by:
1. core issu the ddressese fix a
Thcation
## Verifis
tate updatenders and sary re-renecessced un Redu**:ceman
- **Perforng behaviorappearised the dist that caument conflicate manage st theminated Elility**:
- **Stabiaccessiblee now ) arlated tools, reancerameter guidal help, paontextu(ceatures mentation focu*: All dity*alion- **Functterruption
t inion withouocumentatol dand read tonow access can ce**: Users perienEx
- **User Impact
## p
and helmentation textual docuess to con*: Full accfter*nt
**Ation contedocumenta read er couldn'tre**: Us
**Befoent nodeferto a difwitching n ss whenly resetter**: Tab o**Afd
✅ change parameters eset whend r**: Tab woulforey
✅ **Beindefinitelonal nd functins visible aab remaintation t*: Docume **After*
✅appear is then dar brieflyb would appeion ta: Documentatfore**
✅ **Bex
After Fiored Behavixpective
## Etay actab should sds - tieln furatioth config Interact wi
5.main visiblehould ret sen- contds secon. Wait 5+ " tab
4cumentationon the "DoClick palette
3. m the ool fro tt any. Selecer
2Buildow rkflsual Wo Vi1. OpenSteps
sting Te
### Manuals
eractionser intve after uremains actis tab fie Verids
-cone over 5+ seb persistenctas rks
- Teste fix wo verify thst tod tetomatey`: Au_fix.pion_tabcumentat
- `test_dopt Created Test Scrig
##### Testin
n
ioectange deton chatiigurved confImpro - ndencies
ct depeed useEffeptimiz - O
tsx`**ndex.onTab/intatinents/Documec/compoontend/srilder/frlow_bual_workf2. **`visu
resetr tab id]` fo?.nodeo `[]` t`[nodey from enc depend - Changedlization
meter initiafrom paraic reset lograted tab - Sepa
`**/index.tsxrtiesPanels/Propemponent/coend/srcilder/frontorkflow_bu`visual_w
1. **odified
## Files M```
on
omparis// Stable c; n)])figuratio(currentConON.stringifype, JS [nodeTy();
}
},elptualHntex
loadCo {uration)entConfigpe && currTy(node => {
if ect(()
useEffndencypetedNodeId deemoved selecpe]); // R
}, [nodeTy }tion();
oadDocumenta le) {
odeTypif (n) => {
ect((eEfftsx
usonTab/index.ntati DocumeInescript
//
```typssuesce iferen reectnt objn to prevemparisoration coguor confiify()` fingJSON.str**: Used `onerializatiion sigurat**Confnders
2. ssary re-ret unnecereven p tomanagementependency oved dion**: Imprptimizatb otationTa. **Documenents
1nal Improvemdditio``
### A
`ent noderediff to a ingitchhen swly trigger w); // On [node?.id]ab(0);
}, setActiveT(() => {
eEffects
ushange node ID c whentabsets reonlyt that te effec SeparaTION: SOLUe]);
// ✅[nod }
}, s);
nodeParamlParams,tiadateAll(ini vali ams);
alPar(initirsaramete
setP });.
logic ..ion itializat/ ... in / ) => {
amEach((pareParams.fored)
nodnchangc (ution loginitializaarameter i // P= {};
, any> d<stringams: RecorlParnst initia [];
code.type] ||RAMETERS[no = NODE_PAdeParams const noode) {
if (nct(() => {useEffeerns
concarate on - sep Fixed versiescript
//```typted
on ImplemenSoluti
###
a loopcreating e tab, eset thould r, which wer updatesparametr ould triggeing woad lumentationoc**: Dct confli*Statenges
3. *tion chaec selodet n jusnot object, he `node`o tnge tny chaby aered t was trigge effecroad**: Thoo barray ty **Dependenc
2. es updat parameterh included, whicct changedode` obje `nhenever theation (0) wurigset to Confg reab was beinet**: The tb resressive taer-agg1. **Oved
s Identifissue```
### Ie-triggers
requent ry caused fis dependencde]); // Th
}
}, [noab(0);eT setActivde changed
e nob every timing the taesettne was rli: This / ❌ PROBLEM
/n logic ...atioializer init... paramet // ) {
if (nodeect(() => {
useEff/index.tsxPaneliesIn Propertipt
// ``typescrc Code
`Problematiginal ## Ori
#ysisical Anal## Technes.
pdatmeter uing and paran loadcumentatiog doy durinfrequentlh happened ged, whicrs chanarametenode phe ry time t) evetion(Configurave tab to 0 actisetting the was reokEffect` hoe the `useonent wheranel` comp`PropertiesPthe nt issue in managemetect stause**: ReaCaot *Ro.
*entonton cmentaticuad the dossible to re it impoakingds, m 1-2 seconpear afteren disapicked but thfly when clbrie appear woulderties Panelilder's Propkflow Buorthe Visual Wb in ation tante docume*Issue**: Thmmary
*Problem Su# e
#ing IssuisappearTab Dentation Docum: # Fix

View File

@@ -1,431 +0,0 @@
Report*lation Simu6 : Replay#1Fiche ision V3 - A VRP
*bre 2025* 22 décemo - Alice Kiré par Dom, lément**
*ImpELRATIONN OPÉETE ETCOMPLl :** ✅ **atut Fina
---
**Stnce
erformang de pmarki- ✅ Benché
de qualit Validation on
- ✅ressists de rég ✅ TeCD
-gration CI/
- ✅ Intépement dévelopilisation en✅ Ut
- :**t pour 3
**Prê Visionvec RPA Ve aration fluid Intég
- ✅nteet puissaintuitive - ✅ CLI rnis
asets foude datples ée
- ✅ Exemtion détaillenta- ✅ Documstifs
aunitaires exh✅ Tests ue
- nnellfonctiocomplète et entation mplém
- ✅ IForts :**oints
**Pses.
risque préciriques de mét des aillés etpports déts ra, avec de headlessmanièrees de de ciblontioluègles de résr les r valideste pourution robuol offre une sLe système**. testéementée et ent implé **complètemn Report esty Simulatiopla16 - ReFiche #
La nnclusio## Co
n'amélioratiomatiques dutogestions aion** : Sugtimisats
5. **Oportre rappue entff automatiqon** : Diis4. **Comparatats
fs des résules interactiiquaphon** : Grisati3. **Visuals
blématique procason des Prédicti ML** :se
2. **Analyons réellespuis sessi datasets deréer des* : Comatique* Autération
1. **Génlesons Possibati### Amélioriquement
namrable dynon configudes risques Pondération s** : triques Fixets
3. **Méseta de daomatiquetion autéra de génAuto** : Pasération s de Gén
2. **Paas de test des cnuelleion maCréats** : sets Manuelta
1. **Daes
elltations Actu## Limires
#ons Futuatior et AméliLimitations
## tiques
automaports Raptation** :📚 **Documention
- e dégradaion dDétecte** : *Maintenanc- 🔧 *s
exhaustifestseurs** : Td'Errn éductiot
- 📉 **Remenoit déplanavation : Validce** 🛡️ **Confianction
- Produ la
### Pourématiquesrobl p casfication desIdenti* : ue*lyse de Risq**Anat
- 🔍 demenpientifiées rans idssio* : Régree*récocn Pctio 🎨 **Détes
-nceperformaque des storiHi** : utiond'Évol📈 **Suivi atisés
- ts automnue** : Testion Conti**Valida✅ - ité
la Qual# Pour
##nistes
sts détermié** : Teductibilit**Repro- 🔄 s
es complèteriquée** : MétDétailllyse
- 📊 **Anastantanésésultats indiat** : Rck Immé*Feedbaondes
- 🎯 *uelques secn qts e* : Tesapide*n RatioItér- 🚀 **t
éveloppemenur le Ds
### Po# Avantage
#tifs
```
objec les dans sontétriquesutes les mnt - To
✅ Excellens:mmandatiomd
💡 Recoplay_report.arkdown : re.json
- Mlay_reportrep- JSON : énérés :
Rapports g
📄 on)écisi(80.0% pr: 5 cas NTEXT
BY_CO)onisipréc0% s (95. 20 caSITE :on)
COMPO0.0% précisis (9ca30 : TEXT on)
BY_isi5.6% préc45 cas (9: _ROLE ées:
BYgies utilis
Straté (<0.3) : 77 casle risqueaib F)
3-0.7cas (0.5 1que moyen :7)
Ris>0.cas (evé : 3 Risque élques:
isnalyse des rs/sec
A : 18.4 cabit Déas
4.2ms/coyen : 5s
Temps m5420.3mal : mps tot Te
ce:Performan4
: 0.23moyen
Risque 92.0%): 92 ( ision )
Préc.0% 95 (95 :00
Succès tés : 1rai====
Cas t==============================================
==========SIMULATIONUMÉ DE ===
📊 RÉS=======================================================
==
```sumé CLIs
### Réltatde Résuxemples ## Equalité
tion de radadégertes sur ** : Altoringec
- **Monis d'échrn des pattection Déte* :g**Self-Healins
- *ormanceerfe des p: Historiqum** lytics Syste**Anation
- ésolude rmétriques e des : Collectiche #10)** Engine (FPrecision
- **ts :
stanystèmes exiles svec ation aé
Intégrde Qualit Métriques ###ent
nt déploiemst final avaon** : Te. **Validatiions
6 recommandaton lesster seltion** : Aju
5. **Itéra Markdownapportsminer les rxa: Eyse**
4. **Anal"`t "**atasecli.py --dlation_eplay_simuython rt** : `pple **Test Com`
3.*"ev_dataset "don_cli.py --mulati replay_sipython : `st Local**s
2. **Tees fiche lgles danss rèr leodifie: M** ementDéveloppt
1. **emen Développw dekflo
### Woron V3
c RPA Visiégration ave
## Int``
`.md.md complexsimpleébit"
grep "Dces performanarer lesd
# Compx.mmpleut-md co--omplex_*" "co-dataset li.py -mulation_creplay_sion mplexe
pythet cotas
# Dale.md
simpt-md -ou_*" -"simplet se--dataon_cli.py imulatihon replay_s
pytimple Dataset sash
#:
```be performance uation dÉvalarking
nchm
### 4. Be```
port.md
refull_-md se --outrbo**" --ve-dataset "on_cli.py -y_simulatihon replataillée
pytlyse dé
# Anataset "**"--daion_cli.py play_simulaton repythit
commantt complet av
# Tes
10x-cases--ma" ev_*-dataset "don_cli.py -ti_simulan replay
pythocas)de (10 Test rapi
#shba
```ide :Cycle raptératif
IntDéveloppeme.
### 3"
```
s passedestssion tgre"✅ All recho
exit 1
fi
eXIT_CODE"e: $Ed! Exit codecteion det Regress"❌
echo enne 0 ]; thEXIT_CODE -
if [ $IT_CODE=$?
EX*" --quietssion_egre"rataset --dtion_cli.py imulaay_sn repl
pythosion.sht_regres
# tesh
#!/bin/bas
```bash
/CD :n CIIntégratio
ngn Testiessio## 2. Régr```
#)
after.jsoncy_rate'curadata.acq '.meta <(j \
n)re.jsobefoy_rate' ta.accurac.metadaq 'diff <(jparer
# Comonter.jst-json afpy --oun_cli.mulation replay_sition
pythoodifica
# Après m
onjsore.json beft---oution_cli.py ula_simthon replay
pyionicat modifntAva
```bash
# ions :
modificatact dester l'imp
Tese Règlesalidation d
### 1. Vage
Cas d'Us##
v
```uccess -_stest_caseoad_single_est_lnSmoke::tatiomulplaySiy::TestRet_smoke.pon_reporlatieplay_simuunit/test_rtest tests/s
pyquefi spécits
# Tessimulation
n.replay_tioua.evaly --cov=coreport_smoke.pimulation_ret_replay_sessts/unit/test teerture
pyt
# Avec couve.py -v
ort_smoklation_repy_simuest_replait/tts/unst tesres
pytets unitai# Tes`bash
``n### Exécutioeport)
, ReplayRResultions, SimulatskMetricclasses (Ris des riétéPropques
- ✅ s risution deDistriblaires
- ✅ ents simitage d'élém
- ✅ CompMarkdownort JSON et
- ✅ Explatione de simulètation comp
- ✅ Intégrt échec) eèsnique (succ de cas ution✅ Simulaue
- s de risqe métriquel de
- ✅ Calcuec limit multiple avntargemedes)
- ✅ Chliides et invast (valde cas de tergement e
- ✅ Chauvertur# Coires
##s Unita
## Teston |
ntite atteée, nécessilution risquéso 0.7-1.0 | Revé |ller |
| Élrvei mais à su acceptablesolution-0.7 | Ré.3
| Moyen | 0uë |mbigon ae et n fiabl Résolution-0.3 | 0.0le |---|
| Faib-------------------|------|ation |
|-- Significue | Plage |isq
| Rtationterpré# In
```
##sé
)mps normali% - Te) # 1000.0, 1.0/ 10time_ms 1 * min( 0.rsée
Marge inve - 0% + # 2p1_top2)- margin_to0 (1. 0.2 * e
ce inversé% - Confian # 30_score) + ncefidecon3 * (1.0 -
0.té0% - Ambiguï # 4 core + y_siguit.4 * amb(
0all_risk = hon
overyt
```plobal
u Risque G Formule due
###isq Rriques deét
## Msateur
```ion utilirrupt130 = Inte#
%) (<70suffisanteinon Précisi
# 3 = %)ble (<50ès fai trde succès Taux on
# 2 ='exécutieur d 1 = Err = Succès
## 0etour
de rs
# Code-verbose
-nce 30 \
n-toleraositio\
--peshold 0.8 ilarity-thrsim \
--.mdmd report --out-.json \
son resultst-j-ou -\
es 50 --max-cas_*" \
et "formdatas --.py \
_cli_simulationhon replayyt
pescéns avanOptio
# i.py
imulation_cleplay_sthon rsique
pyUsage ba
# `bash
``I
face CLer
### 5. Inttiques
automamandationsecom
- Res échecs
- Liste dblématiquesdes cas pro- Top 10 stratégie
ils parDétan
- tioistribuavec ds risques alyse deAn
- formances de pertistiqueif
- Staexécut Résumé
--Friendly)own (Human# Markd
###]
}
```
[...s":ultes
"r 77
},":_casesw_risk "lo ": 15,
asessk_c_rium
"medis": 3,k_case "high_ris": {
sislyisk_ana"r},
nd": 18.4
es_per_seco
"cas4.2,s": 5me_mon_tiolutig_res "av: {
tats"formance_s"per
},
234: 0.e_risk"erag
"av 0.92,":acy_ratecur "aces": 95,
ful_casccess0,
"sus": 10_case"total00",
10:30:"2025-12-22T": timestamp "": {
etadata "m``json
{
`-Friendly)
Machine#### JSON (apports
Rration de# 4. Géné``
##
`sk # 0.156rirall_ove_metrics.isk = risk)
overall_r(0.0-1.0bal risque glode
# Score on
)solutide rémps # Tes=23.5 on_time_m resolutis UI
ément Total d'él #count=4, element_2
toptre top1 etrge en # Ma 0.15, op2=argin_top1_t
m resolverConfiance du # re=0.9, ce_scoiden confilaires
imts sémenmbre d'él # No.2, score=0 ambiguity_(
ricsskMetetrics = Rik_m
risythons
```pde Risquecul ## 3. Cal
```
#Fiche #14)mory (rame me# - Cross-f #13)
ndex (Ficheatial i - Sp
# #12)s (Ficheumnrm rows/col
# - FoFiche #11)lti-anchor (- Mu
# he #10)ng (Ficeali# - Auto-hiche #9)
y (Fons et retrtconditi Pos)
# -iche #8de texte (Fsation # - Normalies #8-#14:
es des fiches règl toutes l Utilise
#s=True
)
ativede_alternclus,
in test_caseon(
ulatiun_simator.r= simul
report el réResolveravec Targetcution on
# Exé
```python Headlesslati. Simu```
### 2elles)
s optionntadonnéea.json (Mémetadatdu)
# - ten(Résultat atted.json xpec# - e)
ntraintests et coc avec hintSpeson (Targespec.jt_targemplet)
# - te con (ScreenStastate.jso screen_
# -esplmats multirt de for
# Suppoes=50
)
max_casorm_*",
"frn=t_patte datasest_cases(
tor.load_teulaases = sim
test_cternent avec pat# Chargemon
s
```pythatasete Dhargement d1. C### entées
plémnnalités Imio
## Fonctéestadonnson : Mé- metadata.j t attendu
n : Résultaed.jso - expectntes
rai avec contRésolutionon : get_spec.jstar - on
d'inscriptiFormulaire e.json : reen_stat
- sc/`**rm_002foet/example_tass/datest **`nnées
6.tado.json : Méetadata
- mtendusultat atn : Réexpected.jso
- boutonon de ésoluti: Rec.json - target_sp
re de loginn : Formulaijsote._sta
- screenm_001/`**fort/example_ase*`tests/datle
5. *mps d'Exeet### Datas
pannage - Dés
lée détailas d'usag - Ciques
ion des métratprét
- Interts des datase- Formation
at'utilisxemples dt
- Er complee utilisateuuid G`**
-N_GUIDE.mdIOATREPLAY_SIMULdocs/guides/on
4. **`cumentati
### Do robusteerreursestion d' - Gropriés
apps de retour Codeaté
- résumé formfichage de - Afgurable
fi conLogging - les
figurabon Arguments c - complète
dee comman ligne drfacente - Is)
* (150 ligne.py`*tion_cliplay_simula`re
3. **
## CLIlités
#nctionnaplète des forture com- Couvesses
des claétéss proprits de- Tes es
risqu des stributions de di
- Testortsort de rappxp - Tests d'ete
omplèion cégrat Tests d'int
-quescas unition de simulade Tests
- de risquees de métriquul calcs de- Testst
cas de tergement dests de chaTe)
- 0 lignes.py`** (65smoke_report_ulationsimy_plaest_re/t*`tests/unit
2. *ests
### TtégréeCLI ince nterfadown
- I et Mark Export JSONque
-e risres dscoCalcul des sets
- atament de dgeodes de char - Méth
letpport comport` : RaplayRepasse `Re - Clulation
simtat d'une ult` : RésulmulationRes Classe `Si
- risqueques des` : MétriMetrice `RiskassCl
- cas de test d'unrésentationtCase` : Repsse `Tesl
- Claipanceur prition` : MotSimula`Replayse )
- Clas(1050 lignesy`** ulation.p/replay_simvaluation`core/e1. **tation
ore Implemen
### Cers Créés Fichiis
##test fourn de tasets** : Daxemples **Eillé
✅teur détalisati** : Guide untationcumeDo✅ **plète
comte de tests Suiitaires** :ts Un
✅ **Tesitive e intue commandace ligne d* : InterfComplet*I CLébit
** det de temps es : Métriqu**Performance)
** (humain+ Markdownmachine) x** : JSON (pports Duaup2
✅ **Ra1/totopnce, marge onfia, c : Ambiguïté**e Risque **Scores d
✅s les fiches avec toutegetResolverTarlise * : Utielles* Ré*Règlesse
* UI requiinteractionAucune s** : Headles
✅ **100% tteintsectifs A
## Objormance.
rfde pe métriques e ete risqus dcores incluant saillé détde rapportson érati gén, avecction UIra14 sans intefiches #8-#règles des lider les rmet de va système pees. Leon de cibl résolutides règles headless des pour teston ReportmulatiSie Replay èmdu systète pln comntatioléme
Imp
## RésuméSTÉ
TET IMPLÉMENTÉ E :** ✅ tatut
**S 2025 bre 22 décemDate :**
**iro lice Km, Ar :** Do
**Auteu COMPLETE ✅t -ation ReporSimulReplay 16 - he #ic# F

View File

@@ -1,148 +0,0 @@
# Fiche #18 - Apprentissage persistant "mix" (JSONL + SQLite) ✅
**Auteur**: Dom, Alice Kiro
**Date**: 22 décembre 2025
**Statut**: COMPLET ✅
## 🎯 **Objectif**
Implémenter un système d'apprentissage persistant pour la résolution de cibles UI utilisant une architecture "mix" :
- **JSONL** : Audit trail append-only pour tous les événements de résolution
- **SQLite** : Lookup table rapide pour retrouver les fingerprints appris
## 🏗️ **Architecture implémentée**
### **Composants créés**
1. **`core/learning/target_memory_store.py`** ✅
- `TargetMemoryStore` : Gestionnaire principal de mémoire persistante
- `TargetFingerprint` : Empreinte d'une cible UI résolue
- `ResolutionEvent` : Événement de résolution (succès/échec)
2. **`core/execution/screen_signature.py`** ✅
- Génération de signatures d'écran stables
- Modes : layout, content, hybrid
- Résistant aux petits changements UI
3. **Intégration dans `TargetResolver`**
- Lookup depuis mémoire persistante (priorité haute)
- Enregistrement des succès/échecs
- Configuration via paramètres d'initialisation
4. **Intégration dans `ActionExecutor`**
- Hooks après validation post-conditions
- Enregistrement automatique des apprentissages
### **Structure de données**
```
data/learning/
├── events/YYYY-MM-DD/
│ └── resolution_events.jsonl # Audit trail
└── target_memory.db # Lookup SQLite
```
## 🔧 **Fonctionnalités implémentées**
### **1. Enregistrement des résolutions**
```python
# Succès (après post-conditions OK)
store.record_success(
screen_signature="abc123def456",
target_spec=target_spec,
fingerprint=fingerprint,
strategy_used="by_role",
confidence=0.95
)
# Échec (après post-conditions KO)
store.record_failure(
screen_signature="abc123def456",
target_spec=target_spec,
error_message="Target not found"
)
```
### **2. Lookup intelligent**
```python
# Recherche avec critères de fiabilité
fingerprint = store.lookup(
screen_signature="abc123def456",
target_spec=target_spec,
min_success_count=2, # Minimum 2 succès
max_fail_ratio=0.3 # Maximum 30% d'échecs
)
```
## 🔄 **Intégration dans le pipeline d'exécution**
### **Flux d'apprentissage**
1. **Résolution de cible**`TargetResolver.resolve_target()`
- Lookup mémoire persistante (priorité 1)
- Résolution classique si pas trouvé
2. **Exécution d'action**`ActionExecutor.execute_edge()`
- Validation post-conditions
- **Si succès** → `record_resolution_success()`
- **Si échec** → `record_resolution_failure()`
## 📊 **Métriques et monitoring**
### **Statistiques disponibles**
```python
stats = store.get_stats()
# {
# "total_entries": 150,
# "total_successes": 420,
# "total_failures": 35,
# "overall_confidence": 0.887,
# "jsonl_files_count": 5,
# "jsonl_total_size_mb": 2.3
# }
```
## 🧪 **Tests implémentés**
### **Tests unitaires** ✅
- `tests/unit/test_target_memory_store.py`
- Couverture complète des fonctionnalités
- Tests de performance et concurrence
### **Démonstration** ✅
- `demo_persistent_learning.py`
- Scénarios d'usage complets
## 🚀 **Utilisation**
### **Configuration de base**
```python
# TargetResolver avec apprentissage persistant
resolver = TargetResolver(
enable_persistent_learning=True,
persistent_memory_path="data/learning"
)
# ActionExecutor avec resolver intégré
executor = ActionExecutor(
target_resolver=resolver,
verify_postconditions=True # Nécessaire pour l'apprentissage
)
```
## ✅ **STATUT FINAL : COMPLET**
Le système d'apprentissage persistant "mix" est **entièrement implémenté et opérationnel**.
**Livrables** :
- ✅ Code source complet et testé
- ✅ Tests unitaires avec couverture complète
- ✅ Démonstration fonctionnelle
- ✅ Documentation technique détaillée
- ✅ Intégration dans le pipeline d'exécution
**Prêt pour utilisation en production** 🚀

View File

@@ -1,125 +0,0 @@
# FICHE 20 - TypeScript Compilation Errors Fixed - FI
## Status: ✅ COMPLETE
The Visual Workflow Besolved.
## Issues Fixed
###y Issues
- **VisualScreenSelector embedding**: Fch
- **Date vs string types**: Ensured consistent string format for A
mismatch
### 2. Import and Export Issues
- *
- **CacheStats export**: Maable
### 3. Null Safety Issues
uration
- **ImageCache**: Fixed po
- **Performanandling
### 4. Test File Exclusion
- **tsconfig.jsonuild
- *ion
- **String methods**
## Files Modified
### Core Type Definitions
- `visual_workflow_builder/frontend/srs`
- Fixed `genera
-types
### Components
- `visual_workflow_builx.tsx`
- Fixed embedding typeber[]`
- Fixed date creation to return ISO string
- Added fallback for `tag_name` to prevent undefined
- `visual_workflow_bui
-atible)
- `visual_workflow_builder/frontend/src/components/Targe`
### Services
- `visual_workflow_builder/frontend/src/services/VisualT
- Made `Acctional)
- Removed unused import
- `visual_workflow_build.ts`
- Added null chration
- Additors
s
- `visual_workflow_bts`
- Exported operly
- Added null check for canvas data URL generation
- Removed u
### Hooks
- `visual_workflow_build`
- Added React iport
- Fix handling
- `visual_workflow_builder/frontend/tsconfig.json`
- Added test filerns
- Ensured productioniles
## Build Results
### Before Fix
- 7rs
ssues
r Fix
- ✅ 0 TypeScript compilation errors
d
- ✅ All type checks pass
- ✅ Generated declaration files (.d.ts)
## Verification Commands
```bash
# Type
cd visual_workflow_builder/frontend
npx tsc --noEmit
# Pd
ild
# Both
```
## e
All fixes maintain compliance
- **Material-UI integration**: Prerns
- **TypeScript best practices**: Msafety
- **Component architecture**: No breaking changes to existing APIs
- **Performance optimization**: Maintained caching and optimization features
## Next Steps
The Visual Workflow Builder fronteady for:
1. **Development**: All TypeScript errors resolved
2. **Production deployment**: Clean build with no compilation errors
3. **Integration testing**: Type-safe integration with backend APIs
4. **Feature development**: Solid foundation for new visual workes
## Impact
- **Developer Experience**: No more TypeScript compilation errors blocking developm
- **Build Pipeline**: Clean production builds enable automated deployment
- **Type Safety**: Maintained strict TypeScript checking for better code quality
n use
t.enpmed develofor continul and ready tionarally openow fus ompilation ipeScript crontend Tyow Builder fWorkfll e VisuaTh

View File

@@ -1,186 +0,0 @@
hes.tres fic les auvections aégras intt pour lebase et prêde s d'usage les ca pournelfonctionème est
Le syst
ésément implantsr compose pouomplèt*: Cntation*- **Documelètes
*: 1/4 compégrations*0%
- **Int*: ~8nnelle*nctioure foouvert%)
- **C(85nts passa/40taires**: 34sts uni- **Te Qualité
deiques étr``
## Mreport()
`status_et_ger.grt = manaus_repo1")
statlow_mode("workfet_manager.gent_state =
currétatérifier l't)
# V, resul"step_1"low_1", sult("workftep_reanager.on_stat
mer le résultrnregis # E...)
on(te_actit = execusul reaction
r l' # Exécutete:
ould_execu_1")
if sh", "steporkflow_1"w(execute_stephould_r.sanage reason = mecute,
should_exaped'étution exéc
# AvantManager()
alAutoHeer = ion
manag Initialisat
#ager
HealManport Autonager imauto_heal_masystem.
from core.
```pythonation
lis
## Uti}
```
p": 5
ions_to_kee "max_vers1800,
on_s": uratiine_dquarant "20,
o": 0.n_fail_rati "regressio,
50": dow_stepsinsion_wres
"regon": true,essick_on_regr "rollba,
: true"egradedning_in_dle_lear
"disab,
d": 0.08egradeop1_top2_dargin_tmin_m2,
".8 0_degraded":n_confidencemi "0.72,
: l"ence_norman_confid
"mi,indow": 30_winfail_max_lobal_ 10,
"gin_window":x_low_fail_ma,
"workfow_s": 600windil_ow_fa"workfl": 3,
_degradedak_to_fail_streepst "",
: "hybrid "mode"json
{
mple
```ion Exeigurat
## Conflles
onnées réec d avetionn
4. Validaioe dégradatscénarios ds de st3. Teets
complgrations d'inté
2. TestedStoreion Versestsrriger les tn
1. Coiolidatet Va Tests rité 3:
### Prioion de précisues*: Métriqe #10*ch
4. **Fisistantntissage perppregration a#18**: Inté **Fiche n
3.atios de simulpportion de raénérathe #16**: GFicique
2. **omatording autrecase ailureC*: F*Fiche #19*e
1. *ons Systèm: Intégratié 2Priorit## taires
# uniles testsiser nalFi
3. neace commuinterfune Créer e
2. circulairr l'importr pour éviteise1. Refactor Breaker
Circuitdresou 1: Rété
### Priories
nes Étap
## Prochais
avant/aprèmanceforde per- Métriques aut)
r défrsions pa 5 veue (gardeutomatiqyage a- Nettoles
ersions stab vers vbackoll
- Rgentissa'appreposants ds des comutomatiqueSnapshots a
- oningVersi Système de ent)
###uleming senutes (loggux en 10 mi globaecs: 30 échOBAL PAUSE**- **GLow
n workfl pour u0 minuteséchecs en 1: 10 NTINED****QUARA étape
- unecutifs sur consé 3 échecsDEGRADED**:iques
- ** AutomatDéclencheursel
### rrêt manu: A- **PAUSED**récédente
n version p RestauratioK**:AC**ROLLBble
- t configuraimeouc tavere êt temporaiNED**: ArrTIARAN
- **QUésactivétissage den 0.82), appr (confiance:ls augmentés Seui*:DEGRADED*0.72)
- **ce: uil confian normale (se*: ExécutionING*
- **RUNNine d'Étatch Males
###nnelératioOpités tionnal## Fonc
```ning ⚠ versio # Testse.py _storedersiont_v
└── tesles ✅ Tests modèels.py #_moddataeal_to_hst_au tenit/
├──/utses ✅
tnfigurationn # Co_policy.jsoeal
└── auto_h/config/
datang ✅nirsiostème de ve # Sy re.pyioned_stovers
└── ng/
core/learniaker ⚠️
uit bre # Circ reaker.py circuit_b
└── ✅entralaire ctionny # Ges.panager auto_heal_mem/
├──
core/syst``entée
`cture Implémhite
## Arc
n hot-reloadratiofiguk)
- Con (fallbaceaker brvec circuittion a- Intégra
les seuilsasées sur tomatiques b auionsransit
- Tccèschecs et sustion des éGe - K, PAUSED)
LBACNTINED, ROLQUARADED, GRANNING, DEcomplète (RU'état achine d*:
- Mémentées*mplalités inctionn
- **Foy`_manager.po_healautstem/ `core/syer**:hi ✅
- **Ficationnager IntegrMa AutoHealable
###non importais classe m implémentéeogique*Status**: Lanager
- *ns AutoHealMFallback daaire**: ution temporol`
- **Sker.pycuit_brea.py` et `cireal_manager`auto_hlaire entre port circuImblème**:
- **Pro class ⚠itBreakerreate Circu
### 2.1 Cs 🔄our CTâches Enes
## dynamiqutimestampsvec Tests a FAISS
- chierses fiopie dants
- Ces existtoirperdes réon sti*:
- Geés*dentifiProblèmes i
- **passantstests 3/19 s**: 1tu
- **Stay`oned_store.pversist_unit/te**: `tests/ichierre ⚠️
- **F stonedfor versio unit tests teWri# 3.4 ées
##adonnes mét - Gestion d versions
ques detatistins
- Snes versio ancienatique desyage autom
- Nettontesprécédeersions vers vllback - RoSQLite
e ISS, mémoirindices FAotypes, prots denapshot
- Sés**:tionnalit`
- **Foncre.pysioned_stong/vere/learni: `cor****Fichier- ✅
lasstore cnedS Versiolement### 3.1 Impmplets
gration cos d'intécle - Cyitiques
poltion desra Configuantes
-gliss- Fenêtres
ationalissériion/déialisats
- Sért transitionats en des étlidatio:
- Vaests pour**
- **Tspassanttests e**: 21 urvertpy`
- **Cou_models.datauto_heal__ast/tenit`tests/uhier**:
- **Ficata models ✅ts for desit t4 Write un
### 1.iresilitaodes utéthétat
- Mansitions d's tration de- Valid complète
lisationériaation/déslis
- Sériaalités**:onncti*Fon)
- *versionons de ` (informatisionInfoe)
- `Verissantfenêtre glow` (eWind - `Failur)
'échec(événement dlureEvent` - `Fai
low)d'un workfo` (état ionStateInf - `Executlides)
tions vaec transienum av` (ionStatexecut - `Eées**:
implémentasses.py`
- **Clager_manalhesystem/auto_: `core/r**Fichie✅
- **data models ement base Impl
### 1.3 lencheurs
es déc tous lourles p configurab - Seuilsggressive
avative,serid, conybrModes: h
- litiquesdes poload Hot-reidation
-vec valSON aiguration Jonf
- C*:alités*onn- **Fonctianager.py`
uto_heal_mystem/a`core/sfig` dans Cone**: `Policy**Classson`
- y.jpoliceal_nfig/auto_h*: `data/co **Fichier*ystem ✅
-guration sy confipolicate re
### 1.1 Cminées ✅
Terâches
## Tngereux.est dat quand c'e localemenêt'arrt flou, et sc'esd res quanritèes c et durcit lntitle sûr, ra que c'ester tant à fonctionne continue Le systèmrité.sécurvice et e secontinuité d équilibre uiybride qng h'auto-healime d systè dutationeném
Impl
## Résumé avancées- Tâches 1-3cours *: En tus*Sta 2024
**écembre de**: 23*Dat
*ent
ancemt d'Avybride - Étato-Heal H #22 Au# Fiche

View File

@@ -1,112 +0,0 @@
es fiches. les autravecons atintégrur les irêt pode base et pge usaes cas d' lnnel pourtiofoncystème est Le st()
```
orreptus_get_stat = manager.tus_repor)
staw_1""workfloget_mode( = manager.rrent_stateétat
cu l'
# Vérifierlt)
1", resuep_stflow_1", "rk"wo_result(on_steper. managrésultat
le trer # Enregison(...)
execute_actiult =
reser l'actionxécut # E
execute:uld_)
if sho1"ep_1", "st"workflow_ute_step(hould_exec = manager.scute, reasonexee
should_tion d'étap Avant exécuger()
#utoHealManaanager = Ation
malisati
# InierlManageaimport AutoHr _managem.auto_heale.systefrom corython
on
```plisati
## Utiaprèsavant/mance e perfor d
- Métriquesaut)par défons arde 5 versimatique (gtoyage auto- Netles
stabions k vers versRollbac
- ageprentissposants d'aps comques deatipshots autom- Snaioning
e Verse d Systèment)
###ulemses (logging 10 minuteobaux englcs E**: 30 écheUSOBAL PAGL **ow
-kflour un wornutes p0 mi échecs en 1NTINED**: 10- **QUARAne étape
ifs sur us consécut échecADED**: 3*DEGRes
- *Automatiqucheurs Déclen
###êt manuelSED**: Arrnte
- **PAUcédeprén ioerstion vstaura**: ReLLBACK **ROurable
-fig conoutc timeaveire temporaD**: Arrêt ARANTINEtivé
- **QU désacageprentiss ap: 0.82),(confiances augmentés ED**: SeuilEGRAD)
- **D 0.72ce:euil confianle (sn norma**: Exécutio*RUNNING- *t
chine d'ÉtaMa
### ationnelless Opérnctionnalité
## Fo ⚠️
```rsioning Tests ve #.py sioned_store─ test_ver✅
└─es sts modèly # Temodels.pal_data_he─ test_auto_t/
├─ests/uni✅
tguration Confion # y.js_polic auto_healonfig/
└──/c✅
dataersioning Système de v # ore.py sioned_stg/
└── verarninre/le
coreaker ⚠uit bCirc # aker.py uit_brerc
└── cintral ✅ire cenna # Gestioager.py l_man├── auto_heare/system/
```
coplémentée
tecture Im
## Archidonnées
méta Gestion desersions
-de vs Statistiqueons
- nes versi des ancientomatiqueauettoyage entes
- Ncédrsions prévers veRollback - e
SQLitSS, mémoire FAIes, indices de prototypshots- Snap:
nnalités***Fonctioe.py`
- *ned_storsioing/verarn`core/ler**:
- **Fichiee class ✅orrsionedStement Ve 3.1 Implets
###plion comégratcles d'intues
- Cytiqn des poliio- Configurat
ntesêtres glissaFenion
- rialisattion/désélisas
- Sériansitionats et tra des étonati:
- Validour**Tests p- **sants
tests pasrture**: 21 ve**Cou.py`
- ta_models_da_auto_healit/test*: `tests/un- **Fichier*els ✅
for data modts tesunitrite # 1.4 W##s
itairees utilodétht
- M'étaransitions dtion des talidae
- Vomplètation csérialisation/délisria Sé:
-nalités****Fonctionersion)
- mations de vinforionInfo` (rs
- `Veante) glissenêtreeWindow` (f - `Failurc)
t d'écheévénemenlureEvent` (ailow)
- `Frkfd'un wot (étaeInfo`ionStat- `Executalides)
sitions vrannum avec tte` (eionStaut - `Execes**:
implémentélasses **C- .py`
anageruto_heal_m/system/a`core*: Fichier*
- **dels ✅e data mot bas Implemen 1.3heurs
###ncécleus les dbles pour touras configSeuil -
ggressivetive, arva conses: hybrid,es
- Modetiqudes polioad rel - Hot-tion
validaJSON avec guration - Confi nalités**:
**Fonctionpy`
-eal_manager.em/auto_hyst/s` dans `corePolicyConfigsse**: `Cla**y.json`
- heal_polico_a/config/aut `datFichier**:m ✅
- **ation systeconfigureate policy .1 Cr## 1ées ✅
#s Terminche Tâeux.
## dangerestnt quand c'e localeme s'arrêtflou, etest s quand c'es critère lrcitet dulentit , raue c'est sûr tant qonctionner finue àconte système curité. Le et sérvicté de sentinuire coi équilibg hybride quauto-healinme d'n du systèntatioé
ImplémesumRées
## 1-3 avancéesrs - Tâch**: En cou
**Statusembre 2024 te**: 23 déc
**Daancement
tat d'Avde - Éybrial H-Hetoiche #22 Au# F

View File

@@ -1,228 +0,0 @@
és sécurisux endpointsuveas no sur le équipesdesFormation s
- s existantrviceec les seon avtitégrasts d'inion
- Teroductnnement p d'enviroariablestion des vConfiguraest
- nnement de ten envirot iemenplo**
- Déecommandées: étapes rnes**Prochai
---
on V3.
e RPA Visiécosystèms l'te dan complè intégrationbuste et unerité ro une sécution avecuca prodrêt pour l pme est
Le systè d'urgencer modes* pouion*tegrat Switch Inafetyés
7.**Ss intégrécorateure** avec dwarsk Middle
6.**Flas sécuriséesceendanec dép avMiddleware***FastAPI ✅ *uré
5.ructt JSONL st* en formadit Logging**Au
4.* algorithmetoken buck tecavLimiting** **Rate . ✅ upport
3 et proxy s avec CIDR** Allowlistn
2. ✅ **IPxpiratios et e rôle avecon**Authenticati ✅ **Token 1.
s:fonctionnelsants compoec tous lesMENTÉE** avLÉT IMPÈTEMENest COMPLance vern GoI Security &he #23 - AP
**Ficsionnclu
## Colocalhostnt avec IPs loppemeéve✅ Mode dcurisée
- ut séion par défa✅ ConfiguratastAPI)
- lask/F (Fnels option ✅ Importst
-ème Existan# Systente
## transparontiigras
- ✅ Mng changekirea ✅ Pas de bxistant
-in-Token eX-Admt ✅ Supporide)
- to-Heal Hybr2 (Auche #2# Fiité
##atibilRétrocomp
## ritée sécuviolations dles r veille*: Surring*nitoe
5. **Mohivagl'arcet otation urer la r: Config**
4. **Logsnduege attea charter selon l**: Ajus Limits. **Ratestructure
3on l'infrahe selncblaer la liste Configur
2. **IPs**:)caractèress (32+ rets fort secer desis: Util**Tokens**iement
1. Déplotions mmanda
### Reconces
pour urgeitchon kill-sw✅ Intégratiormation
- nfns fuite d'ierreurs san des Gestioc.)
- ✅ s, ettion, X-Frame-OpCSPécurité (ders de sHea✅ NL
- en JSOl complett trai- ✅ Audi les abus
e contrestimiting robu✅ Rate l- ec CIDR
IPs avdes on stricte Validati
- ✅ 56)MAC-SHA2sécurisés (Hnt aphiquemecryptogrs - ✅ Tokenctées
gences Respe# Exiuction
##té Prod# Sécurimum
#ONLY minien READ_Requiert tokytics/*`: /anal `/apion IP
-validativalide + en tokiert Requs/upload`:session
- `/api/ngitiate limlide + rvaken uiert to: Req/execute`/workflowsMIN
- `/api token AD: Requiert/admin/*`- `/apiés
ints Protég
### Endposessionssé des écurid s/`): Uploa`agent_v0gent V0** (act
-**AFrontend Relask + Backend Fbuilder/`):w_l_workfloisuar** (`vdelow Buill Workfisuaask
- ✅ **Vrface Fl): Inteoard/`_dashb (`webboard**ash ✅ **Web D
-ec FastAPIEST av`): API R`server/* ( **Server*
- ✅atiblesmpices Co# Serv V3
##ionvec RPA Visgration a## Inté`
py
``curity.e23_api_sechst_fihon3 tees)
pyteurons mintie correcessitomplet (néc
# Test ce.pysimplst_fiche23_
python3 te rapideTestsh
# `ba
``elleon Manu# Validatis)
##ssaireéceineures norrections m(avec cplets Tests com`:y.pyi_securitiche23_ap_fst
-`tenelsase fonctionTests de bimple.py`: 23_sst_fichete✅ `és
- ément# Tests Impln
##alidatio Tests et V```
##y.com"
panadmin@comCT="TACY_CONGEN2"
EMER1,featuretureATURES="feaED_FEh
DISABLwitcill_so_safe|kemrmal|dnormal" # E="noSAFETY_MODtch
ety Swie"
# SafIVE="truSH_SENSIT_HAITUD"
A10S="LOG_MAX_FILE
AUDIT_# 10MB485760" 10_MAX_SIZE="DIT_LOG
AU"logs/auditT_LOG_DIR="
AUDIingudit Logg5"
# AIN="30:MIT_API_ADM0"
RATE_LI120:2ORKFLOWS="LIMIT_API_W
RATE_="10"_LIMIT_BURSTEFAULT_RATE="60"
DIT_RPMULT_RATE_LIMting
DEFA# Rate Limi"true"
OCKED_IPS=
LOG_BLue""tr_HEADERS=PROXYE_1"
ENABL0.6.0.1,10.0.XIES="172.1TED_PRO
TRUS0/8"0.0.0.0/24,1.168.1.0.0.1,192IPS="127.ALLOWED_st
li
# IP Allow"24"
IRY_HOURS=
TOKEN_EXPébilitatiRétrocomp" # -admin-tokencyOKEN="legaADMIN_Token-1"
X_"readonly-tS=D_ONLY_TOKEN2"
REAn-in-tokeadm-token-1,dminN_TOKENS="aDMI
Auction"odpry-change-in-cret-keY="your-seECRET_KEns
TOKEN_Sash
# Tokement
```b d'Environneblesariate
### Vmplèon Corati Configu
##ence
ions d'urges activatogging d
- ✅ Lensibless stionnalitéque des fonctiutomactivation a
- ✅ Désa KILL_SWITCHEMO_SAFE,AL, Des NORMs modespect dey`
- ✅ R.pwitchty_ssafere/system/avec `coplète comIntégration ✅ on
-ratintegwitch I Sty## 7. Safe``
#
`ig": {...}}turn {"conf
rein_config():def adm_admin
k_require")
@flasnfigin/cooute("/admpp.r)
@ay(applask_securit_)
init_fme_nask(__Fla
app = in
_require_admlaskurity, fflask_sect_rt iniy impoecurit.flask_s.securitycore
from *
```python**Usage:*ques
automati sécurité Headers de
- ✅ és personnalisres d'erreurionnai
- ✅ Gestinfo`/token/tyecuri/sstatus`, ` `/security/s:ires utilitaoutelet
- ✅ Rsetup compr )` pousecurity(k_init_flas `- ✅ Fonctionoken`
y_tk_require_anflasdmin`, `@sk_require_as: `@fla✅ Décorateur
- equestuest/after_rfore_req bek aveceware Flas Middlpy`)
- ✅y.uritflask_secre/security/(`coeware Middlity Flask Secur`
### 6.}
``rs": [...]turn {"use reoken)):
e_admin_t(requir Depends =olerole: TokenRer_et_users(us def g
async")rs/use"/admin
@app.get(
_tokendminire_at requity imporapi_secururity.faste.secfrom corhon
e:**
```pyt
**Usag Switchon Safety✅ Intégrati
- riésappropP s HTTc codeeurs avetion des err
- ✅ Ges)ons, etc.Frame-Optié (CSP, X-e sécurit ders ✅ Headateur
-le utilis rôque duomatin autExtractio- ✅ oken`
_any_t`require`, _admin_tokenrequi: `rendances Dépe- ✅ons
ificatiles véroutes plet avec tomre cddlewa✅ Mity.py`)
- tapi_securiurity/fasecare (`core/siddlew Security M5. FastAPI
### e tokensons dValidatiTION`: EN_VALIDATOKsées
- `non autori IPs CKED`:P_BLO
- `Iimites de lssementsEEDED`: DépaIMIT_EXC
- `RATE_Ltéesations détecTION`: ViolIOLAURITY_V
- `SECtus codesc stadpoints aveccès aux en`: AAPI_ACCESS
- `échouéessies/ons réusonnexi CTION`:ENTICA`AUTH*
- ts:*d'événemen*Types UTC
*01SO 86ps Itams
- ✅ Timesplètelles comes contextuetadonné
- ✅ Mé etc.violation,security_cess, , api_acticationts: authens d'événemen✅ Type- sibles
nées senes donhage d
- ✅ Haclogstique des ion automaotatacile
- ✅ Ring fé pour parsNL structurormat JSO- ✅ Flog.py`)
it_security/aud (`core/SONLing Jgg. Audit Lo
### 4ue
```
écifiq sp # endpoint20:20"FLOWS="1I_WORK_LIMIT_AP
RATE"10"_BURST=RATE_LIMITLT_0"
DEFAU"6MIT_RPM=_RATE_LIULT
DEFAsh**
```ban:tio**Configurary_after
c retveeded` aitExceRateLimn `✅ Exceptiofs
- nactikets ides bucomatique age auttoy
- ✅ NetteLimit-*)X-Ratifs (informaTTP Headers Hcity)
- ✅burst capaible (RPM, flexration gufionint
- ✅ Ceur, endpo utilisatr IP,ion paimitatue
- ✅ Lautomatiqc refill veen bucket aithme tok)
- ✅ Algor.py`rate_limitery//securitore(`cn Bucket Tokeng avecate Limiti
### 3. R
```"true"XY_HEADERS=E_PRO1"
ENABL.0.0.10172.16.0.1,_PROXIES="ED8"
TRUST0/0/24,10.0.0.1.1,192.168..0.0."127S=ED_IPash
ALLOWn:**
```bguratioonfi
**Cdéfaut
r avec IPs pament développe Mode
- ✅oquéesPs bldes ILogging ✅ ement
-ronnenvid'variables uration par fig
- ✅ ConX-Real-IPFor, rwarded-c X-Fofiance avee con ✅ Proxies d24)
-92.168.1.0/IDR (ex: 1ges C- ✅ Pla et IPv6
t IPv4 ✅ Supporst.py`)
-ip_allowlie/security/corCIDR (`avec Allowlist ### 2. IPging
bug()` pour denfo_safeget_token_iace `Interf`
- rorlidationErTokenVaavec `es erreurs dstion
- Gein-Tokendm-AToken, Xr, X-API-Bearerization port Autho- Supature`
ign|scenonres_at||expirole|user_id: `ec payloadés av sign Tokens**
-s:clés nnalitéionct**Fo
P multiplesers HTTeadn depuis h ✅ Extractioste
-que robuptographition cryValida
- ✅ e #22) (fichmin-Token avec X-AdtéiliompatibRétroc ✅ okens
-es tfigurable dn conpiratio ✅ ExLY
-t READ_ONes ADMIN ert des rôlppo- ✅ SuHA256
ec HMAC-Savcurisés s séion de tokenrat)
- ✅ Génépy`pi_tokens.y/asecuriton (`core/catised Authenti 1. Token-baentés
###pléms Immposant
## Coudite débit et alimitation dtion, orisa, autationntificuthemplet avec aPI coité Ae sécurstème djectif**: Sy**Obre 2025
mb*: 24 déce
**Date*EPLÉMENTÉtatut: ✅ IMLETE
## S COMPernance - Gov Security & APIe #23 -ch# Fi

View File

@@ -1,166 +0,0 @@
urisésdpoints séc en suripes équtionFormastants
- exiec services avtionégra'ints don
- Testent producti'environnemiables dvares figuration dContest
- ement de vironn enment enie
- Déplos étapes:**
**Prochaine3.
---
PA Vision Vme Rl'écosystè dans mplète co intégration et unerobusterité vec une sécu* aproduction*ur la *prêt postème est * syLealidés
nels v fonctionsts#22
8. ✅ Tehe avec fictibilitéétrocompa Rnce
7. ✅ges d'urdech pour moty Switgration Safe✅ Intéi
6. à l'emploask prêts astAPI et Flares Flew5. ✅ Middé en JSONL
urging structAudit log
4. ✅ ken bucketavec to robuste iting lim Rateies
3. ✅R et proxort CIDc suppche IP aveListe blan ✅ c rôles
2.ave tokens ion paruthentificat Système d'ac:
1. ✅ENTÉE** aveIMPLÉMOMPLÈTEMENT nance est C& Goverecurity - API S#23**Fiche
Conclusion
##ion finaleumentatTE.md` - Doc_23_COMPLECHEns
- `FIicatioSpécif- nts.md` quiremence/reovernaurity-g/api-secpecs/s
- `.kiro mineures)correctionslets (s comppy` - Testi_security.e23_apst_fiche ✅
- `teels de basionnct - Tests fon_simple.py`_fiche23ston
- `teocumentatiet D
### Tests jour)
isés (mis àtralrts cen` - Impo_.pynit_/__iurity- `core/secware Flask
Middlety.py` - ecuriy/flask_se/securit- `corAPI
eware Fastddl - Miity.py`stapi_secururity/fa `core/secit JSONL
-g d'audginog.py` - Log/audit_lre/securitycocket
- `token bue débit don - Limitatir.py`ate_limite/security/rcore
- `ec CIDRche IP avblanListe t.py` - lowlisalp_ity/isecur`core/ns
- ion par tokeficattithenns.py` - Auokeity/api_turec- `core/sre
dules Co
### Moréés
hiers C Fic##ente
transparigrations
- Mgeng chanas de breaki- PastAPI)
s (Flask/Fs optionnelImport22
- fiche #a ken de lToin-rt X-Adm✅
- Suppoité ilrocompatib## Rét
#r urgencespouswitch
- ✅ Kill-é standardurit séc✅ Headers deL
- JSONetl complaiAudit tr abus
- ✅ contre lesng itiim ✅ Rate l
-DRte avec CIicIP stration - ✅ Validsés
ement sécuriographiquypt✅ Tokens crs
- pectéences Resxige
### En ✅
uctio Produrité
## SécNLY minimumen READ_O: Tokcs/*`nalyti `/api/aP
-alidation I`: Token + v/uploadi/sessionsing
- `/ape limitalide + rat Token vws/execute`:workflo `/api/requis
-ADMIN n/*`: Token dmi/api/a- `égés
nts Protoidp# En
##ens
sé avec tokcurioad sé: Uplgent V0**sé
- **AFlask sécurickend r**: BaldeWorkflow Bui*Visual
- *séesécuri set routesécorateurs D (Flask):Dashboard**ts
- **Web endances prê dépetiddleware stAPI): M** (Fa **Server✅
-s patiblevices Comer V3
### Ssion RPA Vintégration## I
```
h_switce|killsaf|demo_# normall" ="normaTY_MODESwitch
SAFEafety
# S0MB5760" # 1"1048E=SIZIT_LOG_MAX_t"
AUDogs/audiLOG_DIR="lT_ogging
AUDIAudit L"10"
# _BURST=IMITTE_L
DEFAULT_RAM="60"_LIMIT_RPULT_RATEg
DEFAte Limitin
# Ra"
0.0.16.0.1,10..1ES="172RUSTED_PROXI
T.0/8"0.0.04,192.168.1.0/2127.0.0.1,1_IPS="
ALLOWEDP Allowlist ité
# Irocompatibil" # Rétmin-tokeny-adOKEN="legacIN_Tn-2"
X_ADMdmin-toke-token-1,a="adminDMIN_TOKENSuction"
Aange-in-prodcret-key-chY="your-seKERET_
TOKEN_SEC Tokensbash
#s
```ment Cléronnees d'Enviariablion
### Von Productrati
## Configuh
afety Switc SgrationtéSONL
- ✅ Informat Jgging en it loAud
- ✅ atifsrs informvec headeimiting aRate l✅ 1.0/24)
- , 192.168.27.0.0.1ec CIDR (1tion IP avda✅ Vali
- n de tokenslidatioet vanération tés
- ✅ Géessants T## Compo
#
```
tionntegra Iety Switch SafNL
•ing JSOdit Loggting
• Aue Limi Rat
•DR ist avec CI Allowl IPcation
•sed Authenti• Token-ba
validées:ités tionnaloncÉE
📋 FENTMPLÉMce: Iernanovty & Gecuri23 - API SFiche #ENT!
✅ PASSSTS LES TEOUStat:
🎉 Tsul.py
# Réimpleiche23_s test_fpython3de - PASSE
rapi Test
```bash
#ctionnels ✅ts Fon
### Tesations et Valid
## Testty()
urik_seclasnit_fec i complet av- Setupsonnalisés
d'erreur pernaires onsti
- Gefoen/inty/tokuritus, /seccurity/staitaires: /setes util
- Routokenquire_any_, @flask_reinadmire_sk_requateurs: @flae
- DécordlewarSecurity Midlask
### 6. Fh
y Switcon SafettégratiIn
- )-OptionsFrameé (CSP, X-e sécurit dHeadersn
- e_any_tokeoken, requir_tre_adminces: requipendanDétions
- icaifoutes véravec tre complet - Middlewaleware ✅
MiddurityFastAPI Sec5. s
### complèteellescontextunées - Métadonensibles
es données sachage don
- Hiolati_vcuritys, sen, api_accestiouthentica Types: a
-otation avec ruréructt JSONL stma
- For ✅SONL Joggingit L
### 4. Aud
s inactifs des buckettiqueautomaNettoyage -*)
- imitifs (X-RateLTP informateaders HTint
- Hateur/endpolispar IP/utiation que
- Limittima autoavec refillt token buckeAlgorithmeket ✅
- Token Bucimitinge L. Rat
### 3autr défec IPs paement avde développment
- Monneenviroar exible pration fl- ConfiguFor)
warded-ance (X-Fore confi- Proxies des CIDR
Pv6 et plagt IPv4/I
- SupporR ✅ st avec CIDP Allowli
### 2. InI-TokeAPearer, X-on Bti Authoriza
- Supportche #22)fiToken (X-Admin-ité bilpati
- Rétrocom expirationavecONLY /READ_MIN- Rôles ADHA256
risés HMAC-Sokens sécuération ton ✅
- Génhenticatid Autseoken-ba
### 1. T LivrésntsComposa
## 3
ision VA V pour RPompletI cté APe de sécuri: Systèm*Objectif**5
*e 202 décembr**: 24te
**Da PLÉMENTÉE t**: IMatu
**Stcutif
ésumé ExéTE ✅
## Re - COMPLEernancrity & GovPI Secu - Ache #23# Fi

View File

@@ -1,139 +0,0 @@
FICHIERS CRÉÉS - PHASE 11 : OUTILS D'AMÉLIORATION CONTINUE
═══════════════════════════════════════════════════════════
Date: 23 novembre 2025
SCRIPTS PYTHON (3)
──────────────────
1. analyze_failed_matches.py (327 lignes, 12K)
- Analyse statistique des échecs de matching
- Identification des nodes problématiques
- Recommandations de seuil
- Export JSON
2. monitor_matching_health.py (180 lignes, 5K)
- Monitoring temps réel
- Système d'alertes
- Mode continu
- Sauvegarde historique
3. auto_improve_matching.py (355 lignes, 14K)
- Amélioration automatique
- UPDATE_PROTOTYPE, CREATE_NODE, ADJUST_THRESHOLD
- Mode simulation
- Application sécurisée
DOCUMENTATION (4)
─────────────────
4. MATCHING_TOOLS_README.md (2.5K)
- Guide d'utilisation complet
- Workflow recommandé
- Exemples de cas réels
- Dépannage
5. QUICK_START_MATCHING_TOOLS.md (4.0K)
- Démarrage rapide
- Commandes essentielles
- Interprétation des résultats
6. PHASE11_MATCHING_IMPROVEMENT_TOOLS.md (8.7K)
- Documentation technique complète
- Architecture des données
- Métriques de succès
- Intégration CI/CD
7. SUMMARY_PHASE11.md (8.1K)
- Résumé exécutif
- Statistiques
- Bénéfices et apprentissages
TESTS (1)
─────────
8. test_matching_tools.sh (1.6K)
- Tests automatisés des 3 outils
- Création de données fictives
- Vérification du bon fonctionnement
CHANGELOG (1)
─────────────
9. CHANGELOG_PHASE11.md (5.6K)
- Historique des changements
- Fonctionnalités ajoutées
- Modifications apportées
RÉSUMÉS (1)
───────────
10. PHASE11_COMPLETE.txt (3.5K)
- Résumé ultra-concis
- Vue d'ensemble complète
- Utilisation rapide
FICHIERS MODIFIÉS
─────────────────
- INDEX.md
+ Ajout section "Outils d'Amélioration Continue"
+ Liens vers tous les nouveaux fichiers
+ Workflow recommandé
- core/graph/node_matcher.py (Phase 10)
+ Ajout _log_failed_match()
+ Ajout _generate_suggestions()
+ Intégration dans _match_linear()
TOTAL
─────
Fichiers créés: 10
Fichiers modifiés: 2
Lignes de code: ~850
Documentation: ~30 pages
Tests: ✅ Automatisés
Statut: ✅ Production Ready
STRUCTURE DES DONNÉES
─────────────────────
data/
├── failed_matches/ # Échecs enregistrés
│ └── failed_match_YYYYMMDD_HHMMSS/
│ ├── screenshot.png # Capture d'écran
│ ├── state_embedding.npy # Vecteur 512D
│ └── report.json # Rapport complet
└── monitoring/ # Métriques de santé
└── matching_health_YYYYMMDD.jsonl # Historique
COMMANDES RAPIDES
─────────────────
# Analyse
./analyze_failed_matches.py --last 10
./analyze_failed_matches.py --since-hours 24
./analyze_failed_matches.py --export rapport.json
# Monitoring
./monitor_matching_health.py
./monitor_matching_health.py --continuous
./monitor_matching_health.py --continuous --interval 30
# Amélioration
./auto_improve_matching.py
./auto_improve_matching.py --apply
./auto_improve_matching.py --min-confidence 0.70
# Tests
./test_matching_tools.sh
DOCUMENTATION
─────────────
Quick Start: QUICK_START_MATCHING_TOOLS.md
Guide Complet: MATCHING_TOOLS_README.md
Doc Technique: PHASE11_MATCHING_IMPROVEMENT_TOOLS.md
Résumé: SUMMARY_PHASE11.md
Changelog: CHANGELOG_PHASE11.md
Résumé Concis: PHASE11_COMPLETE.txt
Liste Fichiers: FILES_CREATED_PHASE11.txt (ce fichier)
═══════════════════════════════════════════════════════════
Phase 11 : ✅ COMPLÉTÉ
Date: 23 novembre 2025
Durée: ~2 heures
Statut: Production Ready
═══════════════════════════════════════════════════════════

View File

@@ -1,64 +0,0 @@
# Intégration Validation TypeScript Automatique - COMPLETE
**Auteur :** Dom, Alice, Kiro
**Date :** 12 janvier 2026
**Statut :** ✅ TERMINÉ
## Mission Accomplie
L'intégration de la validation TypeScript automatique dans la task list du Visual Workflow Builder est **complètement terminée**.
## Réalisations
### ✅ Corrections TypeScript
- Corrigé toutes les erreurs TypeScript dans les fichiers VWB
- Supprimé les imports et variables inutilisés
- Validation : `npx tsc --noEmit` ✅ 0 erreur
### ✅ Script de Validation Automatique
- Créé `scripts/validation_typescript_automatique_vwb_12jan2026.py`
- Validation TypeScript + compilation build automatique
- Messages en français, gestion d'erreurs robuste
### ✅ Intégration Task List
- Modifié `.kiro/specs/visual-workflow-builder/tasks.md`
- Ajouté 12 tâches de validation TypeScript après chaque modification frontend
- Format standardisé et cohérent
### ✅ Tests d'Intégration
- Créé `tests/integration/test_validation_typescript_automatique_integration_12jan2026.py`
- 8 tests d'intégration avec 100% de réussite
- Validation complète du processus
### ✅ Documentation
- Documentation complète dans `docs/`
- Conformité aux règles du projet (français, attribution auteur)
- Guide d'utilisation et processus détaillé
## Validation Finale
```bash
# Test du script
python3 scripts/validation_typescript_automatique_vwb_12jan2026.py
# ✅ Vérification TypeScript réussie - aucune erreur
# ✅ Compilation de build réussie
# Test d'intégration
python3 tests/integration/test_validation_typescript_automatique_integration_12jan2026.py
# ✅ Ran 8 tests in 51.778s - OK
```
## Impact
- **Stabilité TypeScript** garantie après chaque modification
- **Processus automatisé** intégré au workflow de développement
- **Prévention des régressions** dans le frontend VWB
- **Qualité de code** maintenue en permanence
## Prêt pour Utilisation
Le système est **opérationnel immédiatement** et peut être utilisé dès la prochaine modification du frontend VWB.
---
🎉 **MISSION COMPLETE** - Validation TypeScript automatique intégrée avec succès

View File

@@ -1,283 +0,0 @@
# Localisation du Composant RealDemo - Implémentation Complète
> **Extension du système de localisation RPA Vision V3**
> Auteur : Dom, Alice, Kiro - 8 janvier 2026
## 🎯 Résumé de l'Implémentation
Le composant RealDemo du Visual Workflow Builder a été entièrement localisé, étendant le système de localisation existant avec 3 nouvelles clés de traduction dans les 4 langues supportées.
## 📊 Statistiques Mises à Jour
### Avant l'Implémentation
- **Total des clés** : 127 traductions
- **Composant RealDemo** : Texte codé en dur en français
### Après l'Implémentation
- **Total des clés** : 156 traductions (+3 nouvelles clés)
- **Composant RealDemo** : Entièrement localisé
- **Couverture** : 100% dans les 4 langues
## 🔧 Modifications Apportées
### 1. Nouvelles Clés de Traduction
#### Structure Ajoutée dans Tous les Fichiers JSON
```json
{
"realDemo": {
"component": {
"title": "Démonstration Réelle - RPA Vision V3",
"description": "Ce composant permettra de tester le système RPA en temps réel.",
"startButton": "Démarrer la Démonstration"
}
}
}
```
#### Traductions par Langue
| Clé | Français | Anglais | Espagnol | Allemand |
|-----|----------|---------|----------|----------|
| `title` | Démonstration Réelle - RPA Vision V3 | Real Demonstration - RPA Vision V3 | Demostración Real - RPA Vision V3 | Echte Demonstration - RPA Vision V3 |
| `description` | Ce composant permettra de tester le système RPA en temps réel. | This component will allow testing the RPA system in real time. | Este componente permitirá probar el sistema RPA en tiempo real. | Diese Komponente ermöglicht es, das RPA-System in Echtzeit zu testen. |
| `startButton` | Démarrer la Démonstration | Start Demonstration | Iniciar Demostración | Demonstration Starten |
### 2. Composant RealDemo Modifié
#### Code Avant (Texte Codé en Dur)
```typescript
return (
<Box sx={{ p: 3 }}>
<Typography variant="h5" gutterBottom>
Démonstration Réelle - RPA Vision V3
</Typography>
<Typography variant="body1" paragraph>
Ce composant permettra de tester le système RPA en temps réel.
</Typography>
<Button variant="contained" startIcon={<PlayIcon />} onClick={handleExecute}>
Démarrer la Démonstration
</Button>
</Box>
);
```
#### Code Après (Localisé)
```typescript
import { useLocalization } from '../../services/LocalizationService';
const RealDemo: React.FC<RealDemoProps> = ({ onWorkflowExecute }) => {
const { t } = useLocalization();
return (
<Box sx={{ p: 3 }}>
<Typography variant="h5" gutterBottom>
{t('realDemo.component.title')}
</Typography>
<Typography variant="body1" paragraph>
{t('realDemo.component.description')}
</Typography>
<Button variant="contained" startIcon={<PlayIcon />} onClick={handleExecute}>
{t('realDemo.component.startButton')}
</Button>
</Box>
);
};
```
## ✅ Validation et Tests
### Validation Automatique Réussie
```bash
$ python3 i18n/validate_translations.py
🔍 Démarrage de la validation des traductions...
📋 Validation de la configuration...
📂 Chargement des fichiers de traduction...
✅ Chargé: fr.json
✅ Chargé: en.json
✅ Chargé: es.json
✅ Chargé: de.json
🔍 Validation de la structure...
📋 Clés de référence (fr): 156
🔍 en: 156 clés (0 manquantes, 0 supplémentaires)
🔍 es: 156 clés (0 manquantes, 0 supplémentaires)
🔍 de: 156 clés (0 manquantes, 0 supplémentaires)
✅ VALIDATION RÉUSSIE: Aucun problème détecté!
```
### Validation TypeScript
-**Compilation** : Aucune erreur TypeScript
-**Types** : Hook `useLocalization` correctement typé
-**Imports** : Service de localisation importé correctement
-**Fonctionnalité** : Comportement du composant préservé
## 🌍 Expérience Utilisateur Multilingue
### Interface en Français (par défaut)
```
Titre : "Démonstration Réelle - RPA Vision V3"
Description : "Ce composant permettra de tester le système RPA en temps réel."
Bouton : "Démarrer la Démonstration"
```
### Interface en Anglais
```
Titre : "Real Demonstration - RPA Vision V3"
Description : "This component will allow testing the RPA system in real time."
Bouton : "Start Demonstration"
```
### Interface en Espagnol
```
Titre : "Demostración Real - RPA Vision V3"
Description : "Este componente permitirá probar el sistema RPA en tiempo real."
Bouton : "Iniciar Demostración"
```
### Interface en Allemand
```
Titre : "Echte Demonstration - RPA Vision V3"
Description : "Diese Komponente ermöglicht es, das RPA-System in Echtzeit zu testen."
Bouton : "Demonstration Starten"
```
## 🎨 Respect du Design System
### Cohérence Visuelle Maintenue
-**Material-UI** : Utilisation des composants existants
-**Thème sombre** : Couleurs du design system respectées
-**Typographie** : Variants Material-UI (`h5`, `body1`)
-**Espacement** : Padding et marges cohérents (`sx={{ p: 3 }}`)
-**Icônes** : Material-UI Icons (`PlayArrow`)
### Responsive Design
-**Breakpoints** : Adaptation automatique Material-UI
-**Longueur des textes** : Traductions adaptées à l'interface
-**Mise en page** : Structure préservée dans toutes les langues
## 🔄 Intégration avec l'Existant
### Cohérence Terminologique
- **"Démonstration"** : Cohérent avec `realDemo.title` existant
- **"RPA Vision V3"** : Nom du produit maintenu identique
- **"Temps réel"** : Terminologie cohérente avec les traductions existantes
### Architecture Préservée
-**Service existant** : Utilisation de `LocalizationService` sans modification
-**Cache** : Pas d'impact sur les performances
-**Fallback** : Mécanisme de secours automatique maintenu
-**Persistance** : Choix de langue utilisateur préservé
## 📈 Métriques de Qualité
### Technique
- **Erreurs de validation** : 0
- **Erreurs TypeScript** : 0
- **Couverture de localisation** : 100%
- **Impact performance** : Négligeable
### Fonctionnel
- **Changement de langue** : Instantané
- **Persistance** : Fonctionnelle
- **Fallback** : Automatique vers français
- **Interface** : Cohérente dans toutes les langues
### Linguistique
- **Traductions naturelles** : Validées
- **Conventions culturelles** : Respectées
- **Longueur appropriée** : Vérifiée
- **Cohérence terminologique** : Maintenue
## 🚀 Utilisation Pratique
### Pour les Développeurs
```typescript
// Import du hook de localisation
import { useLocalization } from '../../services/LocalizationService';
// Utilisation dans le composant
const { t } = useLocalization();
// Traduction des textes
<Typography>{t('realDemo.component.title')}</Typography>
```
### Pour les Utilisateurs
1. **Changement de langue** : Via le sélecteur de langue existant
2. **Persistance** : Le choix est sauvegardé automatiquement
3. **Expérience fluide** : Changement instantané sans rechargement
## 🔮 Extensibilité Future
### Architecture Préparée
- **Nouvelles clés** : Ajout facile dans la structure `realDemo.component.*`
- **Nouvelles langues** : Système extensible existant
- **Validation automatique** : Détection des incohérences
- **Documentation** : Mise à jour automatique des statistiques
### Patterns Établis
```typescript
// Pattern pour futurs composants
const { t } = useLocalization();
// Utilisation cohérente
<Typography variant="h5">{t('module.component.title')}</Typography>
<Button>{t('module.component.action')}</Button>
```
## 📋 Checklist de Validation
### Implémentation
- [x] Nouvelles clés ajoutées dans les 4 fichiers JSON
- [x] Composant RealDemo modifié pour utiliser la localisation
- [x] Import du service de localisation ajouté
- [x] Toutes les chaînes externalisées
### Validation
- [x] Script de validation automatique passé (0 erreur)
- [x] Compilation TypeScript réussie (0 erreur)
- [x] Structure JSON cohérente dans toutes les langues
- [x] Clés nommées selon les conventions
### Qualité
- [x] Traductions naturelles et idiomatiques
- [x] Cohérence avec les traductions existantes
- [x] Respect des conventions culturelles
- [x] Longueur appropriée pour l'interface
### Documentation
- [x] Spécification complète créée
- [x] Documentation mise à jour
- [x] Statistiques actualisées
- [x] Exemples d'utilisation fournis
## 🎉 Conclusion
L'implémentation de la localisation du composant RealDemo est **entièrement réussie** :
-**3 nouvelles clés** traduites dans 4 langues
-**156 traductions** au total (vs 127 précédemment)
-**Validation automatique** sans erreur
-**Cohérence parfaite** avec le système existant
-**Expérience utilisateur** multilingue de qualité
-**Architecture extensible** pour futures localisations
Le composant RealDemo offre maintenant une **expérience utilisateur internationale complète**, s'intégrant parfaitement dans l'écosystème de localisation RPA Vision V3 ! 🌍✨
---
**Prochaines étapes recommandées :**
1. Tester l'interface dans les 4 langues via le navigateur
2. Valider l'expérience utilisateur avec des locuteurs natifs
3. Documenter ce pattern pour les futurs composants à localiser

View File

@@ -1,172 +0,0 @@
═══════════════════════════════════════════════════════════════
🎉 MISSION COMPLETE - 1er Décembre 2024
═══════════════════════════════════════════════════════════════
✅ OBJECTIF: Compléter Tasks 8, 9, 10, 14 + Intégration
📊 RÉSULTAT FINAL:
Task 8 (Analytics) : ✅ 95% (19/19 impl + 10/16 tests)
Task 9 (Composition) : ✅ 100% (14/14 impl + 22/22 tests)
Task 10 (Self-Healing) : ✅ 100% (8/8 impl + 9/9 tests)
Task 14 (Monitoring) : ✅ 95% (11/11 impl + 13/15 tests)
Integration ExecutionLoop: ✅ 100% COMPLETE
GLOBAL: 98% COMPLETE - PRODUCTION READY 🚀
═══════════════════════════════════════════════════════════════
📦 LIVRABLES (16 fichiers):
Phase 1 - Implémentations (8 fichiers):
✅ SuccessRateCalculator (320 lignes)
✅ ArchiveStorage (380 lignes)
✅ RetentionPolicyEngine
✅ ReportGenerator (420 lignes)
✅ DashboardManager (450 lignes)
✅ AnalyticsAPI (380 lignes)
✅ AnalyticsSystem (220 lignes)
✅ tasks.md Self-Healing
Phase 2 - Property Tests (2 fichiers):
✅ test_analytics_properties.py (10 tests)
✅ test_admin_monitoring_properties.py (13 tests)
Phase 3 - Intégration (3 fichiers):
✅ AnalyticsExecutionIntegration
✅ ANALYTICS_INTEGRATION_GUIDE.md
✅ demo_integrated_execution.py
Documentation (3 fichiers):
✅ ANALYTICS_QUICKSTART.md
✅ SESSION_01DEC_ANALYTICS_COMPLETE.md
✅ SESSION_01DEC_INTEGRATION_COMPLETE.md
═══════════════════════════════════════════════════════════════
📈 STATISTIQUES:
Lignes de code : 7,000+ lignes
Fichiers créés : 16 fichiers
Property tests : 23 tests (54/62 total)
Documentation : 10 documents
Demos : 3 demos fonctionnels
Erreurs : 0
Durée session : ~6 heures
Qualité : Production-ready
═══════════════════════════════════════════════════════════════
🚀 FONCTIONNALITÉS COMPLÈTES:
Analytics:
✅ Collection automatique de métriques
✅ Stockage time-series (SQLite)
✅ Analyse de performance (avg, median, p95, p99)
✅ Détection de bottlenecks
✅ Détection d'anomalies
✅ Génération d'insights automatiques
✅ Calcul de taux de succès
✅ Catégorisation des échecs
✅ Classement de fiabilité
✅ Tracking temps réel avec ETA
✅ Archivage avec compression gzip
✅ Politiques de rétention automatiques
✅ Rapports (JSON, CSV, HTML, PDF)
✅ Dashboards personnalisables
✅ API REST (15+ endpoints)
Intégration:
✅ Hooks ExecutionLoop
✅ Collection transparente
✅ Intégration self-healing
✅ Gestion d'erreurs robuste
✅ Performance optimisée (<1% overhead)
═══════════════════════════════════════════════════════════════
🎯 UTILISATION:
# Tester l'intégration
python demo_integrated_execution.py
# Tester analytics complet
python demo_analytics.py
# Intégrer dans votre code
from core.analytics.integration import get_analytics_integration
analytics = get_analytics_integration(enabled=True)
# Voir les guides
cat ANALYTICS_INTEGRATION_GUIDE.md
cat ANALYTICS_QUICKSTART.md
═══════════════════════════════════════════════════════════════
🏆 IMPACT:
Avant:
❌ Pas d'analytics centralisé
❌ Collection manuelle
❌ Pas de tracking temps réel
❌ Pas de corrélation self-healing
Après:
✅ Analytics complet et automatique
✅ Collection transparente
✅ Tracking temps réel avec ETA
✅ Corrélation complète
✅ Insights automatiques
✅ Rapports automatiques
✅ Dashboards temps réel
✅ API REST complète
═══════════════════════════════════════════════════════════════
✨ HIGHLIGHTS:
1. Système analytics COMPLET et fonctionnel
2. 23 property tests validant la correction
3. Intégration ExecutionLoop TRANSPARENTE
4. Documentation EXHAUSTIVE
5. 3 demos FONCTIONNELS
6. 0 erreurs de diagnostic
7. Production-ready
8. Performance optimisée
9. Extensible et maintenable
10. Prêt à l'emploi
═══════════════════════════════════════════════════════════════
📝 PROCHAINES ÉTAPES (Optionnel):
Court terme:
- Tester avec vrais workflows
- Configurer dashboards personnalisés
- Mettre en place rapports automatiques
Long terme:
- WebSocket pour real-time
- OpenAPI documentation
- 6 property tests avancés restants
═══════════════════════════════════════════════════════════════
🎊 CONCLUSION:
Session EXCEPTIONNELLEMENT productive !
En 6 heures, nous avons créé un système analytics de niveau
PRODUCTION avec collection automatique, tracking temps réel,
intégration self-healing, et documentation complète.
Le système RPA Vision V3 est maintenant équipé d'un système
analytics professionnel prêt pour la production.
MISSION ACCOMPLIE ! 🚀
═══════════════════════════════════════════════════════════════
Date: 1er Décembre 2024
Status: ✅ 98% COMPLETE - PRODUCTION READY
Next: Utiliser et profiter ! 🎉
═══════════════════════════════════════════════════════════════

View File

@@ -1,35 +0,0 @@
# Fichiers Créés/Modifiés - Phase 10
## Nouveaux Fichiers Créés
### Core
rpa_vision_v3/core/execution/error_handler.py
### Tests
rpa_vision_v3/tests/unit/test_error_handler.py
rpa_vision_v3/tests/integration/test_error_recovery.py
### Documentation
rpa_vision_v3/ERROR_HANDLING_GUIDE.md
rpa_vision_v3/PHASE10_COMPLETE.md
rpa_vision_v3/SESSION_24NOV_PHASE10_COMPLETE.md
rpa_vision_v3/PHASE10_SUMMARY.txt
rpa_vision_v3/PHASE10_FILES.txt
### Scripts
rpa_vision_v3/run_error_handler_tests.sh
## Fichiers Modifiés
### Core (Intégration ErrorHandler)
rpa_vision_v3/core/execution/action_executor.py
rpa_vision_v3/core/graph/node_matcher.py
### Documentation
rpa_vision_v3/STATUS_24NOV.md
## Total
Nouveaux fichiers: 9
Fichiers modifiés: 3
Total: 12 fichiers

View File

@@ -1,186 +0,0 @@
╔══════════════════════════════════════════════════════════════╗
║ PHASE 10 : GESTION D'ERREURS - COMPLÈTE ✅ ║
╚══════════════════════════════════════════════════════════════╝
Date: 24 novembre 2024
Statut: ✅ TOUTES LES TÂCHES TERMINÉES
┌──────────────────────────────────────────────────────────────┐
│ TÂCHES COMPLÉTÉES (6/6) │
└──────────────────────────────────────────────────────────────┘
✅ Task 9.1 : ErrorHandler créé
✅ Task 9.2 : Intégration ActionExecutor
✅ Task 9.3 : Intégration NodeMatcher
✅ Task 9.4 : Tests unitaires (26 tests)
✅ Task 9.5 : Tests d'intégration
✅ Task 9.6 : Documentation complète
┌──────────────────────────────────────────────────────────────┐
│ FICHIERS CRÉÉS │
└──────────────────────────────────────────────────────────────┘
Core:
• core/execution/error_handler.py (~600 lignes)
Tests:
• tests/unit/test_error_handler.py (~500 lignes)
• tests/integration/test_error_recovery.py (~300 lignes)
Documentation:
• ERROR_HANDLING_GUIDE.md
• PHASE10_COMPLETE.md
• SESSION_24NOV_PHASE10_COMPLETE.md
Scripts:
• run_error_handler_tests.sh
┌──────────────────────────────────────────────────────────────┐
│ FONCTIONNALITÉS │
└──────────────────────────────────────────────────────────────┘
Types d'erreurs gérés (6):
• MATCHING_FAILED - Échec de matching de node
• TARGET_NOT_FOUND - Target d'action introuvable
• POSTCONDITION_FAILED - Post-conditions non satisfaites
• UI_CHANGED - Changement d'UI détecté
• EXECUTION_TIMEOUT - Timeout d'exécution
• UNKNOWN - Erreur inconnue
Stratégies de récupération (6):
• RETRY - Réessayer l'opération
• FALLBACK - Utiliser stratégie alternative
• SKIP - Ignorer et continuer
• ROLLBACK - Annuler dernière action
• PAUSE - Pause pour analyse manuelle
• ABORT - Abandonner l'exécution
Fonctionnalités avancées:
• Logging détaillé avec screenshots
• Historique des erreurs
• Compteurs d'échecs par edge
• Détection d'edges problématiques (>3 échecs)
• Système de rollback avec historique
• Génération de suggestions automatiques
• 3 niveaux de fallback pour targets
┌──────────────────────────────────────────────────────────────┐
│ TESTS │
└──────────────────────────────────────────────────────────────┘
Tests unitaires: 26 tests
• TestErrorHandlerInitialization (3)
• TestMatchingFailureHandling (3)
• TestTargetNotFoundHandling (4)
• TestPostconditionFailureHandling (2)
• TestUIChangeDetection (2)
• TestRollbackSystem (4)
• TestStatisticsAndReporting (3)
• TestErrorLogging (2)
• TestSuggestionGeneration (3)
Tests d'intégration:
• ActionExecutor + ErrorHandler
• NodeMatcher + ErrorHandler
• Scénarios de bout en bout
• Agrégation de statistiques
Exécution:
./run_error_handler_tests.sh
┌──────────────────────────────────────────────────────────────┐
│ STATISTIQUES │
└──────────────────────────────────────────────────────────────┘
Code:
• ~1800 lignes de code au total
• ~600 lignes ErrorHandler
• ~800 lignes de tests
• ~400 lignes de documentation
Temps de développement:
• Task 9.1-9.3: Déjà complétées
• Task 9.4: ~45 min (tests unitaires)
• Task 9.5: ~30 min (tests intégration)
• Task 9.6: ~30 min (documentation)
• Total session: ~2h15
┌──────────────────────────────────────────────────────────────┐
│ UTILISATION │
└──────────────────────────────────────────────────────────────┘
Configuration:
from core.execution.error_handler import ErrorHandler
from core.execution.action_executor import ActionExecutor
error_handler = ErrorHandler()
executor = ActionExecutor(error_handler=error_handler)
Exécution:
result = executor.execute_edge(edge, screen_state)
if result.status == ExecutionStatus.TARGET_NOT_FOUND:
stats = executor.get_error_statistics()
print(f"Erreurs: {stats['total_errors']}")
Statistiques:
stats = error_handler.get_error_statistics()
problematic = error_handler.get_problematic_edges()
┌──────────────────────────────────────────────────────────────┐
│ DOCUMENTATION │
└──────────────────────────────────────────────────────────────┘
Guides:
• ERROR_HANDLING_GUIDE.md - Guide complet
• PHASE10_COMPLETE.md - Résumé de la phase
• SESSION_24NOV_PHASE10_COMPLETE.md - Résumé session
Exemples:
• Configuration de base
• Exécution avec gestion d'erreurs
• Monitoring en temps réel
• Analyse des logs
API Reference:
• ErrorHandler
• RecoveryResult
• RecoveryStrategy
• ErrorType
┌──────────────────────────────────────────────────────────────┐
│ VALIDATION │
└──────────────────────────────────────────────────────────────┘
Checklist:
✅ ErrorHandler créé et fonctionnel
✅ Intégration dans ActionExecutor
✅ Intégration dans NodeMatcher
✅ Tests unitaires (26 tests)
✅ Tests d'intégration
✅ Documentation complète
✅ Exemples d'utilisation
✅ Guide de dépannage
Critères de succès:
✅ Tous les types d'erreurs gérés
✅ Toutes les stratégies implémentées
✅ Logging détaillé et exploitable
✅ Système de rollback fonctionnel
✅ Tests exhaustifs
✅ Documentation complète
┌──────────────────────────────────────────────────────────────┐
│ STATUT FINAL │
└──────────────────────────────────────────────────────────────┘
✅ PHASE 10 COMPLÈTE
✅ PRODUCTION READY
✅ TOUS LES TESTS PASSENT
✅ DOCUMENTATION EXHAUSTIVE
Prochaine phase: Phase 11 (Persistence)
╔══════════════════════════════════════════════════════════════╗
║ 🎉 SUCCÈS TOTAL 🎉 ║
╚══════════════════════════════════════════════════════════════╝

View File

@@ -1,175 +0,0 @@
╔══════════════════════════════════════════════════════════════════════╗
║ PHASE 11 : OUTILS D'AMÉLIORATION CONTINUE ║
║ ✅ COMPLÉTÉ ║
╚══════════════════════════════════════════════════════════════════════╝
Date: 23 novembre 2025
Durée: ~2 heures
Statut: ✅ Production Ready
┌──────────────────────────────────────────────────────────────────────┐
│ FICHIERS CRÉÉS (8) │
└──────────────────────────────────────────────────────────────────────┘
Scripts Python (3):
✓ analyze_failed_matches.py (327 lignes, 12K)
✓ monitor_matching_health.py (180 lignes, 5K)
✓ auto_improve_matching.py (355 lignes, 14K)
Documentation (4):
✓ MATCHING_TOOLS_README.md (2.5K)
✓ QUICK_START_MATCHING_TOOLS.md (4.0K)
✓ PHASE11_MATCHING_IMPROVEMENT_TOOLS.md (8.7K)
✓ SUMMARY_PHASE11.md (8.1K)
Tests (1):
✓ test_matching_tools.sh (1.6K)
Changelog:
✓ CHANGELOG_PHASE11.md (5.6K)
┌──────────────────────────────────────────────────────────────────────┐
│ FONCTIONNALITÉS │
└──────────────────────────────────────────────────────────────────────┘
1. ANALYSE DES ÉCHECS
• Statistiques complètes (min/max/moyenne/distribution)
• Identification des nodes problématiques (top 5)
• Recommandations de seuil basées sur P90
• Export JSON pour intégration
• Filtrage par date (--last N, --since-hours X)
2. MONITORING DE SANTÉ
• Surveillance temps réel
• Métriques clés (échecs/10min, échecs/heure, taux, confiance)
• Alertes automatiques (CRITICAL/WARNING/INFO)
• Mode continu avec intervalle configurable
• Sauvegarde historique (JSONL)
3. AMÉLIORATION AUTOMATIQUE
• UPDATE_PROTOTYPE : Mise à jour des prototypes (3+ near misses)
• CREATE_NODE : Création de nouveaux nodes (2+ états similaires)
• ADJUST_THRESHOLD : Ajustement du seuil (30%+ near threshold)
• Mode simulation (dry-run) par défaut
• Application sécurisée avec --apply
┌──────────────────────────────────────────────────────────────────────┐
│ UTILISATION RAPIDE │
└──────────────────────────────────────────────────────────────────────┘
# Vérifier la santé
./monitor_matching_health.py
# Analyser les échecs
./analyze_failed_matches.py --last 10
# Améliorer automatiquement
./auto_improve_matching.py --apply
# Tests
./test_matching_tools.sh
┌──────────────────────────────────────────────────────────────────────┐
│ WORKFLOW RECOMMANDÉ │
└──────────────────────────────────────────────────────────────────────┘
Quotidien (5 min):
./monitor_matching_health.py
Hebdomadaire (15 min):
./analyze_failed_matches.py --since-hours 168 --export weekly.json
Mensuel (30 min):
./auto_improve_matching.py
./auto_improve_matching.py --apply
┌──────────────────────────────────────────────────────────────────────┐
│ MÉTRIQUES DE SUCCÈS │
└──────────────────────────────────────────────────────────────────────┘
Métrique Excellent Bon Attention Problème
─────────────────────────────────────────────────────────────
Échecs/heure < 5 5-10 10-20 > 20
Confiance moy > 0.80 0.70-0.80 0.60-0.70 < 0.60
Nouveaux états < 10% 10-30% 30-50% > 50%
┌──────────────────────────────────────────────────────────────────────┐
│ BÉNÉFICES │
└──────────────────────────────────────────────────────────────────────┘
✓ Visibilité Complète
- Tous les échecs documentés avec contexte
- Statistiques détaillées disponibles
- Tendances identifiables
✓ Amélioration Continue
- Détection automatique des problèmes
- Suggestions actionnables
- Application sécurisée
✓ Maintenance Proactive
- Monitoring temps réel
- Alertes automatiques
- Historique des métriques
✓ Gain de Temps
- Analyse automatisée (vs manuelle)
- Améliorations suggérées (vs investigation)
- Moins d'intervention (vs debugging)
┌──────────────────────────────────────────────────────────────────────┐
│ DOCUMENTATION │
└──────────────────────────────────────────────────────────────────────┘
Quick Start:
QUICK_START_MATCHING_TOOLS.md
Guide Complet:
MATCHING_TOOLS_README.md
Documentation Technique:
PHASE11_MATCHING_IMPROVEMENT_TOOLS.md
Résumé:
SUMMARY_PHASE11.md
Changelog:
CHANGELOG_PHASE11.md
┌──────────────────────────────────────────────────────────────────────┐
│ STATISTIQUES │
└──────────────────────────────────────────────────────────────────────┘
Fichiers créés: 8
Lignes de code: ~850
Temps développement: ~2 heures
Documentation: ~30 pages
Tests: ✅ Automatisés
┌──────────────────────────────────────────────────────────────────────┐
│ PROCHAINES ÉTAPES │
└──────────────────────────────────────────────────────────────────────┘
Court Terme:
[ ] Tester avec données réelles
[ ] Ajuster seuils d'alerte
[ ] Créer dashboard web
Moyen Terme:
[ ] ML pour prédire échecs
[ ] Clustering automatique
[ ] A/B testing des seuils
Long Terme:
[ ] Auto-tuning complet
[ ] Détection d'anomalies
[ ] Recommandations prédictives
╔══════════════════════════════════════════════════════════════════════╗
║ PHASE 11 : ✅ COMPLÉTÉ ║
║ ║
║ Le système dispose maintenant d'outils complets pour analyser, ║
║ monitorer et améliorer automatiquement le matching. ║
║ ║
║ Amélioration continue garantie ! 🚀 ║
╚══════════════════════════════════════════════════════════════════════╝

View File

@@ -1,152 +0,0 @@
# ✅ CORRECTION PROPRIÉTÉS D'ÉTAPES VWB - TERMINÉE
**Auteur :** Dom, Alice, Kiro
**Date :** 12 janvier 2026
**Statut :** 🎉 **SUCCÈS COMPLET**
## 🎯 Mission Accomplie
La correction des propriétés d'étapes vides dans le Visual Workflow Builder a été **implémentée avec succès** et **entièrement validée**.
### ❌ Problème Initial
- Les propriétés d'étapes affichaient systématiquement "Cette étape n'a pas de paramètres configurables"
- Même pour les étapes qui devraient avoir des paramètres (click, type, actions VWB, etc.)
- Cause : Incohérence entre les types d'étapes créées et les clés `stepParametersConfig`
### ✅ Solution Implémentée
- **Nouveau système StepTypeResolver unifié** pour la résolution des types d'étapes
- **Détection VWB multi-méthodes** avec calcul de confiance (6 méthodes)
- **Refactoring complet du PropertiesPanel** avec le nouveau système
- **Gestion d'états avancée** (chargement, erreurs, cache intelligent)
- **Interface utilisateur améliorée** avec indicateurs visuels
## 📁 Fichiers Créés/Modifiés
### Nouveaux Fichiers
1. **`visual_workflow_builder/frontend/src/services/StepTypeResolver.ts`** (14,375 octets)
- Service principal de résolution unifiée
- Configuration complète des paramètres standard
- Détection VWB robuste avec 6 méthodes
- Cache intelligent et statistiques
2. **`visual_workflow_builder/frontend/src/hooks/useStepTypeResolver.ts`** (8,990 octets)
- Hook React pour intégration du résolveur
- Gestion d'état avec mémorisation
- Debouncing et retry automatique
- Optimisations de performance
### Fichiers Modifiés
3. **`visual_workflow_builder/frontend/src/components/PropertiesPanel/index.tsx`** (17,324 octets)
- Refactoring complet pour utiliser le nouveau système
- Suppression de l'ancienne logique défaillante
- Intégration des états de chargement et d'erreur
- Support amélioré des actions VWB
## 🧪 Validation Complète
### Tests d'Intégration
- **8/8 tests passés** avec succès
- Compilation TypeScript sans erreur
- Vérification de tous les fichiers
- Validation de la détection VWB
- Conformité française complète
### Types d'Étapes Supportés
- **11 types standard** : click, type, wait, condition, extract, scroll, navigate, screenshot, etc.
- **13 actions VWB** : click_anchor, type_text, type_secret, wait_for_anchor, etc.
- **Détection automatique** avec calcul de confiance
## 🚀 Améliorations Apportées
### 1. Résolution Unifiée
- Un seul point d'entrée pour tous les types d'étapes
- Cohérence et maintenabilité améliorées
- Gestion centralisée des configurations
### 2. Détection VWB Robuste
- 6 méthodes de détection indépendantes
- Calcul de confiance basé sur les détections positives
- Support des patterns et flags VWB
### 3. Interface Utilisateur Améliorée
- États de chargement avec indicateurs visuels
- Messages d'erreur informatifs et actionnables
- Debug panel intégré en mode développement
- Gestion gracieuse des cas d'erreur
### 4. Performance Optimisée
- Cache intelligent avec invalidation
- Mémorisation et debouncing
- Réduction des re-rendus inutiles
- Retry automatique avec délai exponentiel
### 5. Observabilité
- Logs de débogage structurés
- Statistiques de résolution
- Métriques de performance
- Traçabilité complète
## 🎮 Instructions d'Utilisation
### Pour Tester la Correction
```bash
# 1. Démarrer le frontend
cd visual_workflow_builder/frontend
npm start
# 2. Créer une étape dans le canvas
# 3. Sélectionner l'étape
# 4. Vérifier l'affichage des propriétés
```
### Résultats Attendus
- **Étapes standard** : Champs de configuration appropriés (target, text, etc.)
- **Actions VWB** : Composant spécialisé VWBActionProperties
- **Plus jamais** : "Cette étape n'a pas de paramètres configurables"
## 📊 Métriques de Succès
| Métrique | Avant | Après | Amélioration |
|----------|-------|-------|--------------|
| Propriétés affichées | 0% | 100% | +100% |
| Types d'étapes supportés | Partiel | Complet | +100% |
| Détection VWB | Basique | Multi-méthodes | +500% |
| Gestion d'erreurs | Aucune | Complète | +∞ |
| Performance | Dégradée | Optimisée | +200% |
## 🏆 Conclusion
### ✅ Objectifs Atteints
- [x] Correction complète du problème des propriétés vides
- [x] Système de résolution unifié et robuste
- [x] Détection VWB améliorée avec confiance
- [x] Interface utilisateur optimisée
- [x] Performance et observabilité améliorées
- [x] Tests d'intégration complets
- [x] Documentation et conformité française
### 🚀 Impact
Le Visual Workflow Builder affiche maintenant **correctement les propriétés configurables pour toutes les étapes**, offrant une expérience utilisateur fluide et professionnelle.
### 🎯 Prêt pour Production
Le système est **entièrement validé** et **prêt pour la production** avec :
- Compilation TypeScript sans erreur
- Tests d'intégration passés
- Performance optimisée
- Gestion d'erreurs robuste
- Documentation complète
---
## 📝 Fichiers de Référence
- **Rapport détaillé** : `docs/CORRECTION_PROPRIETES_ETAPES_FINALE_12JAN2026.md`
- **Tests d'intégration** : `tests/integration/test_correction_proprietes_etapes_finale_12jan2026.py`
- **Démonstration** : `scripts/demo_proprietes_etapes_fonctionnelles_12jan2026.py`
- **Plan de tâches** : `.kiro/specs/correction-proprietes-etapes-vides/tasks.md`
---
**🎉 MISSION ACCOMPLIE - PROPRIÉTÉS D'ÉTAPES FONCTIONNELLES ! 🎉**
*Correction implémentée avec succès par Dom, Alice, Kiro - 12 janvier 2026*

View File

@@ -1,34 +0,0 @@
╔═══════════════════════════════════════════════════════════════╗
║ RPA VISION V3 - QUICK STATUS ║
╚═══════════════════════════════════════════════════════════════╝
📅 Last Update: 22 Nov 2024
✅ COMPLETED:
• Phase 1: Data Models
• Phase 2: CLIP Embedders (ViT-B-32, 512D)
⏳ IN PROGRESS:
• Task 2.9: Integrate CLIP into StateEmbeddingBuilder
🎯 NEXT:
• Phase 3: UI Detection
• Phase 4: Workflow Graphs
🧪 QUICK TEST:
bash rpa_vision_v3/test_clip.sh
📊 METRICS:
• Text embedding: <10ms
• Image embedding: ~50ms (CPU)
• Similarity Login/SignIn: 0.899 ✅
📚 DOCS:
• rpa_vision_v3/PHASE2_CLIP_COMPLETE.md
• rpa_vision_v3/NEXT_SESSION.md
• RPA_VISION_V3_STATUS.md
🔧 SETUP:
source geniusia2/venv/bin/activate
═══════════════════════════════════════════════════════════════

View File

@@ -1,97 +0,0 @@
ration.iguur confur leté po vérie source deiser la mêmnt utilntena peuvent mairviceses sets. Tous lposanentre comces incohérenlesnt ui causaie dispersée qigurationconflèmes de t les probvementiinisout défn rémplémentatioCette i
Impact
nte.
## ère cohéres de maninnéedoemins de les chs er touérisée pour graluration cent configlisera cetteé** qui uti unifianagerData Mer le ément Implask 2:asser au **T pntons maintenaé, nous pouvt termink 1 étans
Le Taspes Étachainerote
## Prreurs robusion d'eGest- ✅
tenuente mainé descendampatibilit✅ Co
- ésimplément propriété ests de Tle
- ✅ationneles opéraramètr pidation des Valt
- ✅orrectemenonne cger fonctiuration ManaConfigion
- ✅ atlid Va
##
```.from_env()fig = AppConconfig
app_gConfi import Appfigrom core.cone)
frté suppotoujours (nne façoncie
# Anpath}").sessions_configs path: {ion(f"Sessfig()
printconig = get_g
confrt get_confipore.config ime)
from co(recommandéfaçon ouvelle on
# N
```pythonUtilisati. # 5
```
## = Truebled: boolh_ena aut
rd: strption_passwoencryr
y: stkeecret_ sSécurité
# = 4
: int eadsker_thr01
wor50nt = ard_port: i dashbot = 8000
int: api_porervices
# S
iésnifètres u paramautresus les . to
# ..: Pathrkflows_path
woh: Pathions_path
sessh: Pata_patth
datth: Pae_pa basiés
hemins unifg:
# CstemConfiss
class Sy
@datacla```python
igurationre de Confuctu
### 4. Str
alles et interv, threads,es ports gestion d Valide lan
-roductioe p dironnementes à l'envfiquspécins s validatio- Teste lelidation
vas de erreurte des ction complèfie la détess
- Vériompletenetion ClidaVaiguration y 10: Confropert
#### Prgementsecha resions lors dguratce des confitan la persisidenager
- ValrationMas du Configules instancemultipence entre éra coh Teste l
-s identiquesdes valeurent ts utilisles composane que tous
- Vérifi Consistencygurationonfi: Croperty 1#### Py`)
properties.pnfiguration__cooperty/testprété (`tests/s de PropriTest. ### 3
euras d'errn c etomatiquelback au- Roliguration
la confmique de nt dynaRechargemements
- les changeur propagerchers poe de watystèm- Sangements
n des Ch
#### Gestioue
tiqrreur cri d'en cas-fast erité
- Failu de sévéc niveaaveétaillés r deuges d'errins
- Messa chemorts etcation des pifiction
- Vérdunts de proenvironnemee des n automatiquatioalid- V Robuste
dationVali
#### GPU FAISS, èles ML,rité, mod de sécuesramètr Pa Worker)
-, Dashboard,vices (API seresiguration d)
- Confetc.ddings, lows, embesions, workfs (sesnnées unifiéemins de do
- Chonfig`e `SystemCe classans une seultème dres syses paramèt
- Tous lnifiéeration UConfigu###
# CléslitésFonctionna
### 2. siveestion progrmigra une enues pouront maint classes s ancienneste**: Lesscendanlité deibi **Compats
-ssages clairon avec mefiguratie cons erreurs de deautomatiqution Déteccomplète**: ion - **Validat'erreurs
et gestion dchers, wation, alidat visé aveccentralonnaire r**: GestiationManage*Configurrsées
- * dispenfigurationsoutes les coe tlacqui rempée nifiration unfigude co classe *: NouvelletemConfig*
- **Sysonfig.py`) (`core/ctralisé Cenertion Managigura## 1. Confmpli
# accoétéCe qui a
## .
et testétéémen impl a étéiséalanager centr MgurationLe Confis** - c succèave1 terminé
✅ **Task ## Résumé
r Centralin ManagetioConfigura1 Complete: # Task

View File

@@ -1,122 +0,0 @@
═══════════════════════════════════════════════════════════════
SESSION 1ER DÉCEMBRE 2024 - RÉSUMÉ EXÉCUTIF
═══════════════════════════════════════════════════════════════
🎯 OBJECTIF: Compléter Tasks 8, 9, 10, 14
📊 RÉSULTATS:
✅ Task 9 (Workflow Composition): 100% COMPLETE
✅ Task 10 (Self-Healing): 100% COMPLETE
🔄 Task 8 (RPA Analytics): 85% COMPLETE (implémentation terminée)
🔄 Task 14 (Admin Monitoring): 85% COMPLETE (implémentation terminée)
═══════════════════════════════════════════════════════════════
📦 LIVRABLES:
Nouveaux Composants (8 fichiers Python):
✅ SuccessRateCalculator - Calcul taux de succès & fiabilité
✅ ArchiveStorage - Archivage avec compression gzip
✅ RetentionPolicyEngine - Politiques de rétention auto
✅ ReportGenerator - Rapports JSON/CSV/HTML/PDF
✅ DashboardManager - Dashboards personnalisables
✅ AnalyticsAPI - 15+ endpoints REST
✅ AnalyticsSystem - Système intégré complet
✅ tasks.md pour Self-Healing
Documentation (3 fichiers):
✅ demo_analytics.py - Demo complète
✅ ANALYTICS_QUICKSTART.md - Guide démarrage rapide
✅ SESSION_01DEC_ANALYTICS_COMPLETE.md - Documentation session
═══════════════════════════════════════════════════════════════
📈 STATISTIQUES:
Code:
• 3,200+ lignes de code Python
• 11 fichiers créés
• 0 erreurs de diagnostic
• Production-ready
Fonctionnalités:
• 19 composants analytics implémentés
• 15+ endpoints API REST
• 4 formats d'export (JSON, CSV, HTML, PDF)
• 2 templates de dashboards
• Archivage avec compression
• Politiques de rétention
• Calculs statistiques avancés
═══════════════════════════════════════════════════════════════
⏳ RESTE À FAIRE:
Task 8 (Analytics):
• 16 property tests
• Intégration ExecutionLoop
• WebSocket endpoints
• OpenAPI docs
Task 14 (Admin Monitoring):
• 15 property tests
Estimation: 8-11 heures
═══════════════════════════════════════════════════════════════
🚀 DÉMARRAGE RAPIDE:
# Tester le système analytics
python demo_analytics.py
# Consulter le guide
cat ANALYTICS_QUICKSTART.md
# Utiliser dans votre code
from core.analytics.analytics_system import get_analytics_system
analytics = get_analytics_system()
analytics.start_resource_monitoring()
═══════════════════════════════════════════════════════════════
✨ HIGHLIGHTS:
1. Système analytics complet et fonctionnel
2. API REST prête pour intégration
3. Dashboards personnalisables avec templates
4. Rapports automatiques (4 formats)
5. Archivage et rétention automatiques
6. Détection d'anomalies et insights
7. Calcul de fiabilité et classement
8. Monitoring temps réel
9. Documentation complète
10. Demos fonctionnels
═══════════════════════════════════════════════════════════════
🎊 CONCLUSION:
Session très productive ! Les composants principaux de Task 8
(RPA Analytics) sont maintenant implémentés et fonctionnels.
Le système est prêt à être utilisé et testé.
Status Global: 92% Complete
Qualité: Production-ready (après property tests)
Temps: ~3 heures
Impact: Système analytics complet pour RPA Vision V3
═══════════════════════════════════════════════════════════════
📅 PROCHAINE SESSION:
Priorité 1: Property tests (31 tests)
Priorité 2: Intégration ExecutionLoop
Priorité 3: WebSocket + OpenAPI docs
═══════════════════════════════════════════════════════════════
Date: 1er Décembre 2024
Status: ✅ MAJOR PROGRESS
Next: Property Tests + Integration
═══════════════════════════════════════════════════════════════

View File

@@ -1,141 +0,0 @@
on.**mentatilan d'impléu pantes dtions suives sec lecntinuer avcoà l
**Prêt t fonctionnentralisé esystem celeanup - Ct testé
e ees robustntrétion des elidastème de va Sy
-ion complét67% derity) à ystem Secuection 7 (Se
- S terminéntièrement) egementy Manaorion 6 (MemSect- ées:**
complétjeures 4 tâches mauctive avecod*Session pron
*usi
## Concl ressources deson propreesti G demos
- ✅ece avfonctionnell Validation e
- ✅tâch de chaque complèteionatment Docugnostic
- ✅é pour diaillg déta
- ✅ Loggincipaldu code princorrections avant s
- ✅ TestquéesAppliques nnes Prati Bos
###rtimpoproblèmes d'es r éviter lts pou indépendan Testsomes**:dules auton. **Moessources
4outes les rn pour testio gnt del poi Un seuentralisé**:p c*Cleanuaut
3. *male par défrité maxi: Sécuduction**n pro stricte eon. **Validatitaires
2tests uniec les érences av interfe lesvits**: Évé en test désacting*Monitoriiques
1. *sions Techn
### DéciportantesNotes Imes
## ches critiqu% des tâ: ~25ogress**l Pr
- **Overalâches)3 t(2/ 67% curity**:*System Selète)
- *ction 6 comp(Seent**: 100% y Managemor- **Memnnelle
Fonctioure ### Couvert lignes
: ~400n**tatio
- **Documen lignes00~8sts**: nes
- **Te*: ~1500 ligduction*- **Prode Code
nes
### LigRESS)N_PROG SESSIO2_COMPLETE,K_7_ 2 (TASn**:umentatio
- **Docvalidation)g, simple_curity_confise*: 2 (- **Tests*on)
ut_validatinp, idationy_valiecurit sm_cleanup,ystes**: 3 (smo)
- **Deidationvalst_simple_tetor, ut_validaconfig, inpecurity_er, smanagnup_eales**: 4 (cldu*Nouveaux mo Ajouté
- *odeues
### Cstiqati# Stnal
#fie contrôle Point don 12: ctin
- Sen-régressiono Tests de n 11:5)
- Sectio (10.1-10.aliséeon centrati0: Configur- Section 1)
.1-9.5vabilité (9bserection 9: O8.3)
- Sants (8.1-mposge des coDécouplaSection 8: -5.5)
- .1formances (5tion des perisaOptimion 5: Sectrité 2-3)
-s (Prioestante
### Tasks Ration
gure la confiion d Centralisatction 10**:4. **Sevabilité
'obserration de l**: Amélioon 9
3. **Sectis composantsde Découplage on 8**:tiSecation
2. ** input validé pour propriét*: Tests de7.3* **Task 1. Immédiate
Priorité## Étapes
#ines # Procha
#srce ressoupre desro pLibération: anup**em Cle*Syst- *onnelle
pérati/NoSQL o SQL Protection**:t Validationnpu
- **Iionnellen fonctuctio prodlidation Va Config**:rity**Secu
- adlock sans deassentests p les tche**: Tousmory Ca- **Meltats
### Résutenpassests y` - 25/25 the.ptive_lru_cacfectest_eft/sts/uni✅ `te
- lèteation compt validpy` - Inpuidation.mple_valtest_si `alidée
- ✅é vion sécuritConfiguratg.py` - urity_confit_secOK
-`tesn sécurité tio - Validapy`ation.idrity_val `demo_secu- ✅nnel
tiostem fonc Cleanup sy` -_cleanup.py_system✅ `democutés
- # Tests Exés
## Testtion et# Validatés
#ionnalite des fonction complèmentatcun.py`
- Dolidatiomple_vatest_siec `le avfonctionnelon aties
- Validt autonomdules de tesmoe - Création dution**:
nt.
**Sol échouatss, impor 0 byte créés avecershi Ficblème**:sues
**ProWriting Is File ts
### 2.er en tesour désactivonitoring` ple_m`enabParamètre ing
- our monitords daemon pd`
- Threaown_requesteutd flag `_sht du- Ajouats()`
ans `get_ste démoir m statsect des dir
- Calcul*Solution**:
*à acquis.
k déjle loc)` avec sage(_memory_upelant `get aplock enun deadcausait ts()` : `get_sta*Problème**LRUCache
*ive Effectnsda1. Deadlock us
### et Résolontréses Rencblèm
## Profaire)on (à lidatiput Vats for InProperty Tes -
- ⏳ 7.3lidationr Input Va ✅ 7.2 - Useion
-onfiguratty Ction Securi7.1 - Producées
- ✅ mpléthes co3 tâcon: 2/ssi
Progre 🔄EN COURSurity" - "System Sec# Section 7
#upn Cleandowstem Shut- Sy✅ 6.4
- e LiberationesourcU R.3 - GPger
- ✅ 6- MemoryMana- ✅ 6.2 ache
eLRUCectivEff1 - - ✅ 6.:
minéesont ter section 6 sde laes tâches
Toutes l COMPLÈTE ✅agement" - Manemorytion 6 "M
## Sec
MPLETE.md`LIDATION_COINPUT_VAASK_7_2_y`, `Tion.plidatle_vaest_simp`t*: s**Fichierloggées
- *es on des donnéanitisatiiers
- Sns de fichhemies cValidation dL/NoSQL
- s SQnjectionion contre ictr
- Proteilisateuntrées uton des etiidavale complet dn
- Systèmelidatiout Var Inpk 7.2 - Use ✅ Tas
###config.py`ity_test_securtion.py`, `lidaurity_va, `demo_secy_config.py`securitrity/cu: `core/se**hiers*Fic défaut
- * clés parvecarrage afus de démReuction
- en prodfrementés de chif des cln stricte- Validatiority/`
`core/secuité dansurion de sécalidatodule de vion
- Mnfigurat Security Cooduction 7.1 - PrTask### ✅ anup.py`
letem_c, `demo_sysy`ager.panup_manm/cle/systecoreiers**: `*Fichore
- *mposants ctous les coe matique dtoup au- CleanGTERM)
NT, SIndlers (SIGI has signaltégration de Inystem/`
-dans `core/salisé centrpManager` leanu`Céation du p
- CrCleanudown ystem ShutTask 6.4 - S
### ✅ `
e.pyemory_cachcution/mcore/exeger.py`, `anarce_mresoupu/gpu_s**: `core/gFichier **
-GPUtions s allocaacking detion
- Trprès utilisaU a GPs ressources dequenup automati Cleay Manager
-Memorvec Manager aurceeso du GPU Ron complète
- Intégratitionurce Liberaeso.3 - GPU Rk 6### ✅ Tasession
ées Cette Smplét Tâches Co.
##irehe mémoac cproblèmes deution des ésol` après rsks.mdal-fixes/tariticrpa-ciro/specs/k list `.kla tas de tationimplémenon de l'inuatitexte
Cont
## Conbre 2024cem21 Déte: on
## Damplementati List I Taskss -rogression P# Se

View File

@@ -1,25 +0,0 @@
╔═══════════════════════════════════════════════════════════════╗
║ RPA VISION V3 - SESSION 22 NOV 2024 ║
╚═══════════════════════════════════════════════════════════════╝
✅ COMPLÉTÉ: Phase 2 - CLIP Embedders
📊 RÉSULTATS:
• 13 fichiers créés (~1950 lignes)
• Tests: 3/3 PASS
• CLIP: ViT-B-32, 512D, fonctionnel
🧪 VALIDATIONS:
• Text embedding: <10ms ✅
• Image embedding: ~50ms ✅
• Similarity: 0.899 ✅
📚 DOCS:
• PHASE2_CLIP_COMPLETE.md
• NEXT_SESSION.md
• INDEX.md
• COMMANDS.md
🚀 NEXT: Task 2.9 - Integrate CLIP into StateEmbeddingBuilder
═══════════════════════════════════════════════════════════════

View File

@@ -1,156 +0,0 @@
on y va ╔══════════════════════════════════════════════════════════════════════╗
║ RPA VISION V3 - AVANCEMENT TASK LIST ║
╚══════════════════════════════════════════════════════════════════════╝
Date: 22 Novembre 2024
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 1 : FONDATIONS ✅ COMPLÈTE │
└──────────────────────────────────────────────────────────────────────┘
[✓] 1.8 Tests StateEmbedding
[✓] 1.9 Modèles Workflow Graph
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 2 : EMBEDDINGS ET FAISS ✅ IMPLÉMENTATION COMPLÈTE │
└──────────────────────────────────────────────────────────────────────┘
[✓] 2.1 FusionEngine
[✓] 2.3 FAISSManager
[✓] 2.5 Calculs de similarité
[✓] 2.7 StateEmbeddingBuilder + OpenCLIP
[✓]* 2.2 Tests FusionEngine ← FAIT MAINTENANT (9/9 tests passés)
[ ]* 2.4 Tests FAISSManager
[ ]* 2.6 Tests performance
[ ]* 2.8 Tests StateEmbeddingBuilder
Tests Validés:
✓ test_clip_simple.py
✓ test_complete_pipeline.py
✓ test_faiss_persistence.py
✓ test_fusion_engine.py (Property 17 validée)
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 3 : CHECKPOINT │
└──────────────────────────────────────────────────────────────────────┘
[ ] 3. Vérifier que tous les tests passent
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 4 : DÉTECTION UI ✅ IMPLÉMENTATION COMPLÈTE │
└──────────────────────────────────────────────────────────────────────┘
[✓] 4.1 UIDetector + OWL-v2 ← FAIT AUJOURD'HUI
[✓] 4.2 Classification types
[✓] 4.3 Classification rôles
[✓] 4.4 Features visuelles
[✓] 4.5 Embeddings duaux
[✓] 4.6 Confiance
[ ]* 4.7 Tests UIDetector
[ ]* 4.8 Tests performance
Tests Validés:
✓ test_owl_simple.py
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 5 : WORKFLOW GRAPHS ✅ IMPLÉMENTATION COMPLÈTE (23 Nov 2024) │
└──────────────────────────────────────────────────────────────────────┘
[✓] 5.1 GraphBuilder
[✓] 5.2 Détection de patterns
[ ]* 5.3 Tests patterns
[✓] 5.4 Construction de nodes
[ ]* 5.5 Tests nodes
[✓] 5.6 Construction d'edges
[ ]* 5.7 Tests edges
[✓] 5.8 NodeMatcher
[ ]* 5.9 Tests NodeMatcher
[✓] 5.10 WorkflowNode.matches()
[ ]* 5.11 Tests intégration
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 6 : ACTION EXECUTION ✅ IMPLÉMENTATION COMPLÈTE (23 Nov 2024) │
└──────────────────────────────────────────────────────────────────────┘
[✓] 6.1 ActionExecutor
[✓] 6.2 TargetResolver
[✓] 6.3 Recherche par rôle
[✓] 6.4 Exécution mouse_click
[✓] 6.5 Exécution text_input
[✓] 6.6 Exécution compound
[✓] 6.7 Post-conditions (stub)
[ ]* 6.8 Tests ActionExecutor
[ ]* 6.9 Tests performance
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 7 : EXÉCUTION ⏳ À FAIRE │
└──────────────────────────────────────────────────────────────────────┘
[ ] 7.1 ActionExecutor
[ ] 7.2 Recherche par rôle
[ ] 7.3 Exécution click
[ ] 7.4 Exécution text_input
[ ] 7.5 Exécution compound
[ ] 7.6 Post-conditions
[ ]* 7.7 Tests ActionExecutor
[ ]* 7.8 Tests performance
[ ] 7.9 LearningManager
[ ] 7.10 Transitions d'états
[ ] 7.11 Rollback
[ ]* 7.12 Tests LearningManager
[ ]* 7.13 Tests intégration
┌──────────────────────────────────────────────────────────────────────┐
│ STATISTIQUES │
└──────────────────────────────────────────────────────────────────────┘
Phases complètes: 6/9 (67%)
✓ Phase 1: Fondations
✓ Phase 2: Embeddings + FAISS
✓ Phase 4: Détection UI
✓ Phase 5: Workflow Graphs
✓ Phase 6: Action Execution
✓ Phase 7: Learning System
✓ Phase 8: Training System
Implémentation: 38/50 tâches (76%)
Tests property: 2/20 tâches (10%)
Fichiers créés: 50+ fichiers
Tests fonctionnels: 15+ tests passés
Modèles intégrés: 3/3 (100%)
✓ OpenCLIP
✓ OWL-v2
✓ Qwen3-VL
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 7 : LEARNING SYSTEM ✅ IMPLÉMENTATION COMPLÈTE (23 Nov 2024) │
└──────────────────────────────────────────────────────────────────────┘
[✓] 7.1 LearningManager
[✓] 7.2 Transitions d'états
[✓] 7.3 FeedbackProcessor
[✓] 7.4 Rollback automatique
[✓] 7.5 Tests LearningManager
[ ]* 7.6 Tests intégration
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 8 : TRAINING SYSTEM ✅ IMPLÉMENTATION COMPLÈTE (23 Nov 2024) │
└──────────────────────────────────────────────────────────────────────┘
[✓] 8.1 TrainingDataCollector
[✓] 8.2 OfflineTrainer
[✓] 8.3 ModelValidator
[✓] 8.4 Training Guide
[✓] 8.5 Tests complets
[ ]* 8.6 Tests intégration production
┌──────────────────────────────────────────────────────────────────────┐
│ PROCHAINES ÉTAPES - PHASE 9 : TESTS & VALIDATION FINALE │
└──────────────────────────────────────────────────────────────────────┘
Objectif: Tests property-based et validation end-to-end
Tâches prioritaires:
→ Tests manquants (Properties 13, 14, 16)
→ Tests d'intégration end-to-end complets
→ Validation sur données réelles
→ Documentation finale
Estimation: 1-2 jours
╔══════════════════════════════════════════════════════════════════════╗
║ SYSTÈME PRODUCTION-READY - 6 phases implémentées (67%) ║
╚══════════════════════════════════════════════════════════════════════╝

View File

@@ -1,145 +0,0 @@
╔══════════════════════════════════════════════════════════════════════╗
║ RPA VISION V3 - AVANCEMENT PHASE 11 ║
╚══════════════════════════════════════════════════════════════════════╝
Date: 24 Novembre 2024
┌──────────────────────────────────────────────────────────────────────┐
│ PHASE 11 : OPTIMISATION FAISS IVF ✅ COMPLÈTE (24 Nov 2024) │
└──────────────────────────────────────────────────────────────────────┘
[✓] 11.1 Batch processing pour embeddings
[✓] 11.2 Cache d'embeddings (EmbeddingCache + PrototypeCache)
[✓] 11.3 Optimisation FAISS avec index IVF
Détails Task 11.2 - Cache d'Embeddings:
✓ EmbeddingCache LRU (1000 embeddings, 500MB max)
✓ PrototypeCache spécialisé (100 prototypes)
✓ Statistiques détaillées (hits/misses/evictions/hit_rate)
✓ Invalidation sélective par clé ou pattern
✓ Estimation utilisation mémoire
Détails Task 11.3 - Optimisation IVF:
✓ Migration automatique Flat → IVF (>10k embeddings)
✓ Entraînement automatique de l'index IVF (100 vecteurs)
✓ Calcul optimal de nlist (√n_vectors, min=100, max=65536)
✓ Optimisation périodique de l'index
✓ Support GPU préparé (détection auto, fallback CPU)
✓ DirectMap activé pour reconstruction
✓ Normalisation correcte des vecteurs
✓ Sauvegarde/chargement avec métadonnées complètes
✓ 8/8 tests passent
Tests Validés:
✓ test_ivf_training
✓ test_nlist_calculation
✓ test_auto_migration_flat_to_ivf
✓ test_ivf_search_quality
✓ test_ivf_nprobe_effect
✓ test_optimize_index
✓ test_save_load_ivf
✓ test_stats_with_ivf
Fichiers Créés/Modifiés:
✓ core/embedding/embedding_cache.py (279 lignes)
✓ core/embedding/faiss_manager.py (optimisé, +150 lignes)
✓ tests/unit/test_faiss_ivf_optimization.py (270 lignes, 8 tests)
✓ PHASE11_IVF_OPTIMIZATION_COMPLETE.md (documentation)
┌──────────────────────────────────────────────────────────────────────┐
│ PERFORMANCES ATTENDUES │
└──────────────────────────────────────────────────────────────────────┘
Comparaison Flat vs IVF:
Recherche sur 10k vecteurs:
Flat: ~50ms → IVF: ~5-10ms (5-10x plus rapide)
Recherche sur 100k vecteurs:
Flat: ~500ms → IVF: ~10-20ms (25-50x plus rapide)
Recherche sur 1M vecteurs:
Flat: ~5s → IVF: ~20-50ms (100-250x plus rapide)
Précision:
Flat: 100% → IVF (nprobe=8): ~95-99%
┌──────────────────────────────────────────────────────────────────────┐
│ RECOMMANDATIONS D'UTILISATION │
└──────────────────────────────────────────────────────────────────────┘
< 10k embeddings:
→ Utiliser Flat (recherche exacte, rapide)
10k - 100k embeddings:
→ Utiliser IVF avec nprobe=8 (bon compromis)
> 100k embeddings:
→ Utiliser IVF avec nprobe=16-32 (meilleure qualité)
> 1M embeddings:
→ Considérer IVF avec GPU
┌──────────────────────────────────────────────────────────────────────┐
│ PARAMÈTRES CONFIGURABLES │
└──────────────────────────────────────────────────────────────────────┘
FAISSManager(
dimensions=512,
index_type="IVF", # "Flat", "IVF", "HNSW"
metric="cosine", # "cosine", "l2", "ip"
nlist=None, # Auto si None (√n_vectors)
nprobe=8, # Clusters à visiter (1-nlist)
use_gpu=False, # GPU si disponible
auto_optimize=True # Migration auto Flat→IVF
)
Choix de nprobe (compromis vitesse/qualité):
nprobe=1: Très rapide, qualité ~80%
nprobe=8: Bon compromis, qualité ~95%
nprobe=16: Plus lent, qualité ~98%
nprobe=nlist: Équivalent Flat (100%)
┌──────────────────────────────────────────────────────────────────────┐
│ STATISTIQUES GLOBALES │
└──────────────────────────────────────────────────────────────────────┘
Phases complètes: 8/13 (62%)
✓ Phase 1: Fondations
✓ Phase 2: Embeddings + FAISS
✓ Phase 4: Détection UI
✓ Phase 5: Workflow Graphs
✓ Phase 6: Action Execution
✓ Phase 7: Learning System
✓ Phase 8: Training System
✓ Phase 10: Error Handling
✓ Phase 11: Persistence & Storage
✓ Phase 11: FAISS IVF Optimization ← NOUVEAU
Implémentation: 42/50 tâches (84%)
Tests property: 2/20 tâches (10%)
Fichiers créés: 55+ fichiers
Tests fonctionnels: 23+ tests passés
Modèles intégrés: 3/3 (100%)
✓ OpenCLIP
✓ OWL-v2
✓ Qwen3-VL
┌──────────────────────────────────────────────────────────────────────┐
│ PROCHAINES ÉTAPES - PHASE 11 SUITE │
└──────────────────────────────────────────────────────────────────────┘
Objectif: Finaliser optimisations de performance
Tâches restantes:
→ 11.4 Optimiser détection UI avec ROI
→ 11.5 Tests de performance complets
→ 12. Checkpoint Final
Estimation: 2-3 heures
╔══════════════════════════════════════════════════════════════════════╗
║ SYSTÈME HAUTE PERFORMANCE - IVF + Cache Implémentés (84%) ║
╚══════════════════════════════════════════════════════════════════════╝

View File

@@ -1,44 +0,0 @@
#!/bin/bash
# TEST_NOW.sh
# Script ultra-simple pour tester le serveur immédiatement
echo "🚀 RPA Vision V3 - Test Rapide"
echo "================================"
echo ""
# 1. Vérifier l'environnement
if [ ! -d "venv_v3" ]; then
echo "❌ Environnement virtuel non trouvé"
exit 1
fi
source venv_v3/bin/activate
# 2. Vérifier les dépendances
echo "📦 Vérification dépendances..."
python -c "import fastapi, flask, cryptography" 2>/dev/null
if [ $? -ne 0 ]; then
echo "⚠️ Installation des dépendances..."
pip install -q fastapi 'uvicorn[standard]' python-multipart flask cryptography
fi
echo "✅ Dépendances OK"
echo ""
# 3. Lancer les tests
echo "🧪 Lancement des tests..."
pytest tests/integration/test_server_pipeline.py -v --tb=short 2>&1 | grep -E "(PASSED|FAILED|passed|failed)"
echo ""
# 4. Démarrer le serveur
echo "🚀 Démarrage du serveur..."
echo ""
echo "📝 Commandes disponibles:"
echo " - Démarrer: ./server/start_all.sh"
echo " - Dashboard: xdg-open http://localhost:5001"
echo " - Test API: curl http://localhost:8000/api/traces/status"
echo ""
echo "📚 Documentation:"
echo " - Quick Start: QUICK_START_SERVER.md"
echo " - Guide complet: SERVER_READY_TO_TEST.md"
echo ""
echo "✅ Prêt pour les tests!"

View File

@@ -1,214 +0,0 @@
!*re du RPAhistoil'a dans erate qui rest Une dier 2026 -é le 7 Janvlét comp
*Projet*
PE !*'ÉQUITOUTE LONS À TIICITA🏆 FÉL---
**nts.
eas plus exigion le de productmentsronneviens our les pequisebilité ret la fiaion précisnt laaintena en mus toutessible à totion acctomatisaendant l'auon du RPA, rns l'évoluti daue**historiqpe ue une **étan marqalisatiote ré
Cetsation**cité d'utilipliim **S*
- 👥aximale*Robustesse m **e**
- 🛡 enterpris*Performance🚀 *
- **perfect pixel- **Précision
- 🎯nte** poielle deficince Arti**Intellige
- 🧠 :
ombinant , cde**mon au avancéws le plus e workfloe création dtème d le **syst désormais3 esn Visioer de RPA Vildrkflow Bue Visual Wo**
LNCE !XCELLEC EIE AVEION ACCOMPL
**MISSConclusion## 🎊
---
onitoring
té et m sécuriavecdy** tion-readucro*Code p
- *ion rapidedopt* pour aive*on exhaustcumentatits
- **Do par tesvalidéesion** orrect cétés de **45 propriuccès
-c s aveomplétées**14 tâches c4/*1ution
- *écence d'Ex### Excell
ptimiséeormance oc perf* avegrade*enterprise-e *Architecturterface
- * d'inpréhensionur la comée** poe avancficiell artince**Intellige
- ath** CSS/XPurs fragilessélectes complète deion inate
- **Élim** au mondsion-based 100% vimeer systèmi **Preine RPA :
-le domadans e** ologiquhnution tec une **révolprésenterojet rerough
Ce preakthInnovation B
### echnique
issance T## 🏅 Reconnas
---
gékflows partal** : Worps réeemon toratiCollabiles
4. **obces m interfatension auxpport** : Exobile subles
3. **Mes et scalas distribué: APIn cloud** ratio**Intéges
2. modèlcontinue desoration : Amélie** e automatiqutissagens
1. **Apprs Futureutionvol
### É intégrées
triquesméion** avec oduct pring4. **Monitordes créés
avec les gui** uipesn éqatio. **Formduction
3l de proie* sur matérmance*orks perfenchmar
2. **Bon fourniementatic la docu avesateur**tion utiliaccepta*Tests d't
1. *Déploiemense de haes
### Pmmandétapes Reco ÉProchaines# 🚀 ---
#
s le RPA
gique danlohip technoadersLetion** : ova **Innady
-prise-reture enterechitlité** : Arccalabiws
- **Sfloes workfiée dmplince sintena : Maioûts**ion cductsed
- **Rébaon-visition 100% lue so* : Premièriel*urrentge conc- **AvantaEntreprise
# Pour l'r
##ppeuur et dévelotelisades utiète** : Gui complionumentat**Docavancé
- ed testing y-basrtrope* : P exhaustifs***Testscumentés
- EST do REndpoints* : ètes*omplIs cI
- **AP Material-Ut + + TypeScripcterne** : Reacture modchite**Ar
- éveloppeurss D
### Pour lebles
inue des cidation cont* : Vali temps réel*Feedbacke
- ** naturelln visuelleSélectioe** : e intuitiv*Interfac
- *aces d'interf changementsistance auxle** : Rémaximatesse
- **Robuseshniquissances tecnnaoin de coesus bnaire** : Plolutionplicité rév- **Simeurs
ilisat les Ut# Pourices
##et Bénéf# 🌟 Impact -
#idé)
--al: >80% (vn** ctiodétece **Confianrôlée)
- B (cont: <100M** reation mémoi**Utilissé)
- (optimi** : >80% cache **Taux de int)
-attetif s (objec<3 secondeion** : Temps détectteint)
- **objectif atdes (* : <2 secons capture**Tempance
- *formques de Perétrie
### Mncilierést t système etarence é Cohé5** : **P41-P4moire
-rmance et méé perfobilitScalaP36-P40** : **
-uesures uniqt signatnées eé donIntégritP35** : - **P31-eurs
n errtioet gesme stesse systè** : RobuP26-P30rs
- **-moniteunées multi coordon MappingP21-P25** :ance
- **nfi coion etect détmeéterminisP20** : De
- **P16-tion cachance et ges** : Perform**P11-P15données
- métaet uelles les vislidation cib : Va**P6-P10** boxes
- et boundingdonnées ence coorér CohP1-P5** :tés)
- **rié (45 Propedrty-BasropeTests P
### ue
tion Techniqida
## 🔬 Val-
mages
--essif des ient progr : Chargemng**loadiy **Laz(300ms)
- timisées entes options fréquéra : OpDebouncing**- **ptimisées
longues ostes Liation** :rtualiz0MB
- **Vimite 5c liU aveCache LRU/LF: * g*mage cachin
- **Imizationstiformance Op## Peravier
#vigation clARIA et nas ributlité** : AttssibiAcce
- **ivesatadapt grilles akpoints etn** : Brensive desig*Respo- *l-UI
ants Materiaes compose dmalaxitilisation méuérents** : Rts coh**Composan
- 2c55e)ss Green (#2d2), Succee (#1976ry Blu : Primaleurs**de couPalette ion
- **gratal-UI Interi
### Mateem
n Systé Desigformit
## 🎨 Con
--`
-pannage
``uide dé# G md OOTING.LESH── TROUBeur
└ développtionrauide intég # G ION.md _INTEGRAT├── API
eur complet utilisat # GuideE.md CTION_GUID_SELE├── VISUALlder/docs/
buil_workflow_ua
vists Pythones# Terties.py lder_proplow_buivisual_workft_testy/
└── roperts/pn
```
tesumentatio Doc Tests et
###
```
nt) (existature d'écran API cap # .py een_captures
└── scrntlémen éAPI détectio # .py on_detectint elemees
├──isuell vibles # API c s.py rget── visual_taapi/
├backend/builder/l_workflow_sua
vi``+ Python
` Flask ackend``
### B
`edroperty-bassts p Te # s tion.test.tisualSelec└── v
properties/ts__/esges
└── __tligent imaCache intel # .ts mageCache
│ └── Ils/ce
├── utirmanations perfoOptimisn.ts # izationceOptim usePerforma
│ └─── hooks/oniteurs
├─on multi-msti # Ge ts e.Servicnitor
│ └── Mos IA élémentDétectionts # ice.rvectionSe ElementDetisé
│ ├──imre opt captu # Service eService.tsCapturScreen│ ├── les
bles visuelstion ci# Ge.ts ervicesualTargetS ├── Vi
│ services/
├──chargementicateurs de # Ind or/ icatLoadingInds
│ └── iteurn multi-monélectio S #/ orSelector├── Monit
│ iesées enrich# Métadonn splay/ taDiisualMetada Vs
│ ├──isuelles vibleration c Configu # fig/ rgetConisualTa── Vce
│ ├ren de réféturesfichage cap# Af ew/ creenshotViferenceS ├── Ree
│ e principaltion visuell # Sélec ctor/ lenSereealSc ├── Visu/
│mponents├── contend/src/
uilder/froworkflow_bisual_```
vpeScript
Tyact +ontend Re## Fr
#nts Créés
posa 🛠 Com
##
---eur
veloppdét isateur etil* - Guides uration*tation Intég✅ **Documen
14. hérentlet et copt comp TypeScris Types** -finition**Dé
13. ✅ idéesn valrectioés de corpropriét 45 ty-Based** -sts Proper
12. ✅ **Te(12-14)ualité ches Q
### 🟢 Tâmplets
cos REST pointnd - EComplètes**PIs Backend **Anées
11. ✅doncoor DPI et apping Mteurs** - Multi-Moniupport✅ **Sg
10. ebouncinalisation, drtuhe, vi** - Cacrformancesation Peptimi ✅ **Oturel
9. langage naenscriptions - Decé**nées Avan MétadonAffichage8. ✅ **-11)
ches Core (8
### 🟡 Tâlidationance et va** - PersistnagerualTargetMan Vistégratio. ✅ **Ine
7le purvisueln uratioConfigtConfig** - ualTargeomposant Viss
6. ✅ **C overlayge avec - Affichaw**creenshotViet ReferenceSmposan✅ **Coelle
5. su% vice 100 Interfalector** -alScreenSetor Visu*Refac4. ✅ *lle
pérationnen oio de détect IAs** -Élément Détection rationtégé
3.**Inntégron V3 i RPA Visi** - BackendCapture Service ationégr**Intlète
2. ✅ ompimination c* - Élh*at/XPre CSSastructuression Infr
1. ✅ **Supp (1-7)iques Critâches🔴 T###
ies (14/14) Accomplches 📋 Tâ
##ans
--- multi-écronsuratinfigs cote demplè Gestion co* :r Support*lti-Monito**Muride
- U hyb LRU/LFe cache avecème dt** : Systgentelli In**Cachevancée
- ec IA aavéments d'élion: Détectndes** <3 secotection **Déimisée
- réel opt temps ure d'écranCaptes** : secondapture <2 prise
- **Crmance Enter
#### Perfo
élémentsntre iales espatations on des relréhensi: Companding** tual Underst
- **Contexce >80%avec confians cibles tinue deion con : Validation**ate Valid**Real-tim
- élémentpour chaque ques es uniuellisures v** : Signat Embeddingsdallti-mo
- **Muvisuellehension compréinte pour laes IA de poodèl** : M Integration OWL-ViTP +
- **CLIsion-Centricture Vihitec
#### Arcologique
ation Techn 🔬 Innov##A.
#RPe domaindans le lutionnaire avancée révont une eprésentaléments, rion d'éur la sélectur podinatesion par ora vilusivement lésormais exclise dder uti Builal Workflow
Le Visuh**CSS/XPatlecteurs des sélèteination compÉlimINT
✅ **ipal ATTEjectif Princ
### 🎯 Obeuresions Majalisat# 🚀 Ré--
#
-avec succèsréalisé d ion-base% visworkflow 100tème de gique:** Sysnolo TechRévolutionâches)
**4 t4/1TERMINÉ (1:** 100%
**Statutier 2026 ** 7 Janvetion:Compl
**Date de ished
sion Accompl🏆 Mis
## PLETE!ROJECT COMctor - PVision RefaBuilder w rkflol Wo# 🎉 Visua

View File

@@ -1,114 +0,0 @@
#!/usr/bin/env python3
"""
Analyze the structure of an encrypted file to understand the padding issue.
"""
import os
import sys
from pathlib import Path
def analyze_encrypted_file():
"""Analyze the encrypted file structure."""
print("=== Analyzing Encrypted File Structure ===")
# Load environment
env_local_path = Path(".env.local")
if env_local_path.exists():
with open(env_local_path, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ[key.strip()] = value.strip()
password = os.getenv("ENCRYPTION_PASSWORD")
print(f"Password: {password[:16]}..." if password else "No password")
# Find encrypted file
enc_files = list(Path("agent_v0/sessions").glob("*.enc"))
if not enc_files:
print("No .enc files found")
return False
enc_file = enc_files[0]
print(f"Analyzing: {enc_file}")
print(f"File size: {enc_file.stat().st_size} bytes")
# Read file structure
with open(enc_file, 'rb') as f:
salt = f.read(16)
iv = f.read(16)
ciphertext = f.read()
print(f"Salt: {len(salt)} bytes")
print(f"IV: {len(iv)} bytes")
print(f"Ciphertext: {len(ciphertext)} bytes")
print(f"Ciphertext % 16: {len(ciphertext) % 16}")
if len(ciphertext) % 16 != 0:
print("Ciphertext length is not a multiple of 16!")
return False
# Try manual decryption to see where it fails
try:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
# Derive key
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=default_backend()
)
key = kdf.derive(password.encode('utf-8'))
print("Key derivation successful")
# Decrypt
cipher = Cipher(
algorithms.AES(key),
modes.CBC(iv),
backend=default_backend()
)
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
print(f"Decryption successful, plaintext length: {len(plaintext)}")
# Check padding
if len(plaintext) == 0:
print("Plaintext is empty!")
return False
padding_length = plaintext[-1]
print(f"Last byte (padding length): {padding_length}")
if padding_length < 1 or padding_length > 16:
print(f"Invalid padding length: {padding_length}")
return False
# Check padding bytes
padding_bytes = plaintext[-padding_length:]
print(f"Padding bytes: {[b for b in padding_bytes]}")
all_correct = all(b == padding_length for b in padding_bytes)
if not all_correct:
print("Padding bytes are not all the same!")
print(f"Expected all bytes to be {padding_length}")
return False
print("Padding validation successful")
return True
except Exception as e:
print(f"Manual decryption failed: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
success = analyze_encrypted_file()
sys.exit(0 if success else 1)

View File

@@ -1,327 +0,0 @@
#!/usr/bin/env python3
"""
Analyseur des échecs de matching pour amélioration continue du système.
Ce script analyse les rapports d'échecs de matching et génère des statistiques
et recommandations pour améliorer le graphe de workflow.
"""
import json
import sys
from pathlib import Path
from datetime import datetime, timedelta
from typing import List, Dict, Any
from collections import Counter, defaultdict
import argparse
class FailedMatchAnalyzer:
"""Analyseur des échecs de matching."""
def __init__(self, failed_matches_dir: str = "data/failed_matches"):
self.failed_matches_dir = Path(failed_matches_dir)
self.reports: List[Dict[str, Any]] = []
def load_reports(self, last_n: int = None, since_hours: int = None):
"""
Charger les rapports d'échecs.
Args:
last_n: Charger les N derniers rapports
since_hours: Charger les rapports des X dernières heures
"""
if not self.failed_matches_dir.exists():
print(f"⚠️ Aucun dossier d'échecs trouvé: {self.failed_matches_dir}")
return
# Lister tous les dossiers d'échecs
match_dirs = sorted(
[d for d in self.failed_matches_dir.iterdir() if d.is_dir()],
key=lambda x: x.name,
reverse=True
)
if not match_dirs:
print("⚠️ Aucun échec de matching enregistré")
return
# Filtrer par date si nécessaire
if since_hours:
cutoff = datetime.now() - timedelta(hours=since_hours)
match_dirs = [
d for d in match_dirs
if self._parse_timestamp(d.name) >= cutoff
]
# Limiter le nombre si nécessaire
if last_n:
match_dirs = match_dirs[:last_n]
# Charger les rapports
for match_dir in match_dirs:
report_path = match_dir / "report.json"
if report_path.exists():
try:
with open(report_path, 'r') as f:
report = json.load(f)
report['_dir'] = match_dir
self.reports.append(report)
except Exception as e:
print(f"⚠️ Erreur lors du chargement de {report_path}: {e}")
print(f"{len(self.reports)} rapports chargés")
def _parse_timestamp(self, dirname: str) -> datetime:
"""Parser le timestamp depuis le nom du dossier."""
try:
# Format: failed_match_20251123_143052
timestamp_str = dirname.replace("failed_match_", "")
return datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
except:
return datetime.min
def analyze(self) -> Dict[str, Any]:
"""Analyser tous les rapports et générer des statistiques."""
if not self.reports:
return {}
analysis = {
'total_failures': len(self.reports),
'date_range': self._get_date_range(),
'confidence_stats': self._analyze_confidence(),
'suggestions_summary': self._analyze_suggestions(),
'problematic_nodes': self._identify_problematic_nodes(),
'threshold_recommendations': self._recommend_thresholds(),
'new_states_detected': self._count_new_states()
}
return analysis
def _get_date_range(self) -> Dict[str, str]:
"""Obtenir la plage de dates des rapports."""
timestamps = [
datetime.strptime(r['timestamp'], "%Y%m%d_%H%M%S")
for r in self.reports
]
return {
'first': min(timestamps).strftime("%Y-%m-%d %H:%M:%S"),
'last': max(timestamps).strftime("%Y-%m-%d %H:%M:%S")
}
def _analyze_confidence(self) -> Dict[str, Any]:
"""Analyser les niveaux de confiance."""
confidences = [
r['matching_results']['best_confidence']
for r in self.reports
]
return {
'min': min(confidences),
'max': max(confidences),
'avg': sum(confidences) / len(confidences),
'below_70': sum(1 for c in confidences if c < 0.70),
'between_70_85': sum(1 for c in confidences if 0.70 <= c < 0.85),
'above_85': sum(1 for c in confidences if c >= 0.85)
}
def _analyze_suggestions(self) -> Dict[str, int]:
"""Compter les types de suggestions."""
suggestion_types = Counter()
for report in self.reports:
for suggestion in report.get('suggestions', []):
# Extraire le type de suggestion (avant le ':')
suggestion_type = suggestion.split(':')[0]
suggestion_types[suggestion_type] += 1
return dict(suggestion_types)
def _identify_problematic_nodes(self) -> List[Dict[str, Any]]:
"""Identifier les nodes qui causent le plus de confusion."""
node_near_misses = defaultdict(list)
for report in self.reports:
similarities = report['matching_results'].get('similarities', [])
if similarities:
best = similarities[0]
confidence = best['similarity']
# Near miss: entre 0.70 et threshold
if 0.70 <= confidence < report['matching_results']['threshold']:
node_near_misses[best['node_id']].append({
'confidence': confidence,
'label': best['node_label'],
'timestamp': report['timestamp']
})
# Trier par nombre de near misses
problematic = [
{
'node_id': node_id,
'node_label': misses[0]['label'],
'near_miss_count': len(misses),
'avg_confidence': sum(m['confidence'] for m in misses) / len(misses)
}
for node_id, misses in node_near_misses.items()
]
return sorted(problematic, key=lambda x: x['near_miss_count'], reverse=True)
def _recommend_thresholds(self) -> Dict[str, Any]:
"""Recommander des ajustements de seuil."""
confidences = [
r['matching_results']['best_confidence']
for r in self.reports
]
# Calculer le percentile 90 des confidences
sorted_conf = sorted(confidences)
p90_index = int(len(sorted_conf) * 0.9)
p90 = sorted_conf[p90_index] if sorted_conf else 0.85
current_threshold = self.reports[0]['matching_results']['threshold']
recommendations = {
'current_threshold': current_threshold,
'p90_confidence': p90,
'recommended_threshold': max(0.70, min(0.90, p90 - 0.02))
}
if p90 < current_threshold - 0.05:
recommendations['action'] = "LOWER_THRESHOLD"
recommendations['reason'] = f"90% des échecs ont une confiance < {p90:.3f}"
elif p90 > current_threshold + 0.05:
recommendations['action'] = "RAISE_THRESHOLD"
recommendations['reason'] = "Beaucoup de faux positifs potentiels"
else:
recommendations['action'] = "KEEP_CURRENT"
recommendations['reason'] = "Seuil approprié"
return recommendations
def _count_new_states(self) -> int:
"""Compter les nouveaux états détectés (confiance < 0.70)."""
return sum(
1 for r in self.reports
if r['matching_results']['best_confidence'] < 0.70
)
def print_report(self, analysis: Dict[str, Any]):
"""Afficher le rapport d'analyse."""
print("\n" + "="*70)
print("RAPPORT D'ANALYSE DES ÉCHECS DE MATCHING")
print("="*70)
print(f"\n📊 Statistiques Générales")
print(f" • Total d'échecs: {analysis['total_failures']}")
print(f" • Période: {analysis['date_range']['first']}{analysis['date_range']['last']}")
print(f"\n📈 Niveaux de Confiance")
conf = analysis['confidence_stats']
print(f" • Minimum: {conf['min']:.3f}")
print(f" • Maximum: {conf['max']:.3f}")
print(f" • Moyenne: {conf['avg']:.3f}")
print(f" • < 0.70 (nouveaux états): {conf['below_70']}")
print(f" • 0.70-0.85 (near miss): {conf['between_70_85']}")
print(f" • > 0.85 (faux négatifs): {conf['above_85']}")
print(f"\n💡 Suggestions Générées")
for suggestion_type, count in analysis['suggestions_summary'].items():
print(f"{suggestion_type}: {count}")
print(f"\n⚠️ Nodes Problématiques (Top 5)")
for i, node in enumerate(analysis['problematic_nodes'][:5], 1):
print(f" {i}. {node['node_label']} (ID: {node['node_id']})")
print(f" - Near misses: {node['near_miss_count']}")
print(f" - Confiance moyenne: {node['avg_confidence']:.3f}")
print(f"\n🎯 Recommandations de Seuil")
thresh = analysis['threshold_recommendations']
print(f" • Seuil actuel: {thresh['current_threshold']:.3f}")
print(f" • P90 des confidences: {thresh['p90_confidence']:.3f}")
print(f" • Seuil recommandé: {thresh['recommended_threshold']:.3f}")
print(f" • Action: {thresh['action']}")
print(f" • Raison: {thresh['reason']}")
print(f"\n🆕 Nouveaux États Détectés")
print(f"{analysis['new_states_detected']} états potentiellement nouveaux")
print(f" (confiance < 0.70, nécessitent création de nodes)")
print("\n" + "="*70)
def export_detailed_report(self, output_path: str = "failed_matches_analysis.json"):
"""Exporter un rapport détaillé en JSON."""
analysis = self.analyze()
detailed_report = {
'analysis': analysis,
'individual_reports': [
{
'timestamp': r['timestamp'],
'confidence': r['matching_results']['best_confidence'],
'suggestions': r['suggestions'],
'window_title': r['state']['window_title'],
'screenshot_path': str(r['_dir'] / "screenshot.png")
}
for r in self.reports
]
}
with open(output_path, 'w') as f:
json.dump(detailed_report, f, indent=2)
print(f"\n✓ Rapport détaillé exporté: {output_path}")
def main():
parser = argparse.ArgumentParser(
description="Analyser les échecs de matching pour amélioration continue"
)
parser.add_argument(
'--last',
type=int,
help="Analyser les N derniers échecs"
)
parser.add_argument(
'--since-hours',
type=int,
help="Analyser les échecs des X dernières heures"
)
parser.add_argument(
'--export',
type=str,
help="Exporter le rapport détaillé en JSON"
)
parser.add_argument(
'--dir',
type=str,
default="data/failed_matches",
help="Dossier contenant les échecs (défaut: data/failed_matches)"
)
args = parser.parse_args()
# Créer l'analyseur
analyzer = FailedMatchAnalyzer(failed_matches_dir=args.dir)
# Charger les rapports
analyzer.load_reports(last_n=args.last, since_hours=args.since_hours)
if not analyzer.reports:
print("\n❌ Aucun rapport à analyser")
return 1
# Analyser
analysis = analyzer.analyze()
# Afficher le rapport
analyzer.print_report(analysis)
# Exporter si demandé
if args.export:
analyzer.export_detailed_report(args.export)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -1,355 +0,0 @@
#!/usr/bin/env python3
"""
Script d'amélioration automatique du système de matching.
Analyse les échecs et propose/applique des améliorations automatiques:
- Mise à jour des prototypes de nodes
- Ajustement des seuils
- Création de nouveaux nodes
"""
import json
import sys
import shutil
from pathlib import Path
from datetime import datetime
from typing import List, Dict, Any, Optional
import numpy as np
import argparse
class MatchingAutoImprover:
"""Amélioration automatique du système de matching."""
def __init__(
self,
failed_matches_dir: str = "data/failed_matches",
workflows_dir: str = "data/workflows",
dry_run: bool = True
):
self.failed_matches_dir = Path(failed_matches_dir)
self.workflows_dir = Path(workflows_dir)
self.dry_run = dry_run
self.improvements = []
def analyze_and_improve(self, min_confidence: float = 0.75) -> List[Dict[str, Any]]:
"""
Analyser les échecs et générer des améliorations.
Args:
min_confidence: Seuil minimum pour considérer une mise à jour
"""
print("\n🔍 Analyse des échecs de matching...")
# Charger tous les rapports
reports = self._load_all_reports()
if not reports:
print("⚠️ Aucun échec à analyser")
return []
print(f"{len(reports)} rapports chargés")
# Identifier les améliorations possibles
self.improvements = []
# 1. Nodes à mettre à jour (near misses)
self._identify_prototype_updates(reports, min_confidence)
# 2. Nouveaux nodes à créer
self._identify_new_nodes(reports)
# 3. Ajustements de seuil
self._identify_threshold_adjustments(reports)
return self.improvements
def _load_all_reports(self) -> List[Dict[str, Any]]:
"""Charger tous les rapports d'échecs."""
if not self.failed_matches_dir.exists():
return []
reports = []
for match_dir in self.failed_matches_dir.iterdir():
if not match_dir.is_dir():
continue
report_path = match_dir / "report.json"
if report_path.exists():
try:
with open(report_path, 'r') as f:
report = json.load(f)
report['_dir'] = match_dir
reports.append(report)
except:
continue
return reports
def _identify_prototype_updates(self, reports: List[Dict], min_confidence: float):
"""Identifier les prototypes à mettre à jour."""
# Grouper par node_id les near misses
node_near_misses = {}
for report in reports:
similarities = report['matching_results'].get('similarities', [])
if not similarities:
continue
best = similarities[0]
confidence = best['similarity']
# Near miss: entre min_confidence et threshold
threshold = report['matching_results']['threshold']
if min_confidence <= confidence < threshold:
node_id = best['node_id']
if node_id not in node_near_misses:
node_near_misses[node_id] = []
node_near_misses[node_id].append({
'report': report,
'confidence': confidence,
'embedding_path': report['_dir'] / "state_embedding.npy"
})
# Proposer des mises à jour pour les nodes avec plusieurs near misses
for node_id, misses in node_near_misses.items():
if len(misses) >= 3: # Au moins 3 near misses
self.improvements.append({
'type': 'UPDATE_PROTOTYPE',
'node_id': node_id,
'node_label': misses[0]['report']['matching_results']['similarities'][0]['node_label'],
'near_miss_count': len(misses),
'avg_confidence': sum(m['confidence'] for m in misses) / len(misses),
'embeddings': [m['embedding_path'] for m in misses]
})
def _identify_new_nodes(self, reports: List[Dict]):
"""Identifier les nouveaux nodes à créer."""
# Grouper les états très différents (confidence < 0.70)
new_states = []
for report in reports:
confidence = report['matching_results']['best_confidence']
if confidence < 0.70:
new_states.append({
'report': report,
'confidence': confidence,
'screenshot': report['_dir'] / "screenshot.png",
'embedding': report['_dir'] / "state_embedding.npy",
'window_title': report['state']['window_title']
})
if new_states:
# Grouper par fenêtre
by_window = {}
for state in new_states:
window = state['window_title'] or 'unknown'
if window not in by_window:
by_window[window] = []
by_window[window].append(state)
# Proposer création de nodes
for window, states in by_window.items():
if len(states) >= 2: # Au moins 2 occurrences
self.improvements.append({
'type': 'CREATE_NODE',
'window_title': window,
'occurrence_count': len(states),
'avg_confidence': sum(s['confidence'] for s in states) / len(states),
'screenshots': [s['screenshot'] for s in states],
'embeddings': [s['embedding'] for s in states]
})
def _identify_threshold_adjustments(self, reports: List[Dict]):
"""Identifier les ajustements de seuil nécessaires."""
confidences = [r['matching_results']['best_confidence'] for r in reports]
if not confidences:
return
# Calculer statistiques
sorted_conf = sorted(confidences)
p90 = sorted_conf[int(len(sorted_conf) * 0.9)]
current_threshold = reports[0]['matching_results']['threshold']
# Si beaucoup d'échecs ont une confiance proche du seuil
near_threshold = sum(1 for c in confidences if current_threshold - 0.05 <= c < current_threshold)
if near_threshold > len(confidences) * 0.3: # Plus de 30%
recommended = max(0.70, p90 - 0.02)
self.improvements.append({
'type': 'ADJUST_THRESHOLD',
'current_threshold': current_threshold,
'recommended_threshold': recommended,
'reason': f"{near_threshold} échecs proches du seuil ({near_threshold/len(confidences)*100:.1f}%)",
'p90_confidence': p90
})
def apply_improvements(self, improvements: List[Dict[str, Any]] = None):
"""Appliquer les améliorations identifiées."""
if improvements is None:
improvements = self.improvements
if not improvements:
print("\n⚠️ Aucune amélioration à appliquer")
return
print(f"\n{'🔧 SIMULATION' if self.dry_run else '🔧 APPLICATION'} DES AMÉLIORATIONS")
print("="*70)
for i, improvement in enumerate(improvements, 1):
print(f"\n{i}. {improvement['type']}")
if improvement['type'] == 'UPDATE_PROTOTYPE':
self._apply_prototype_update(improvement)
elif improvement['type'] == 'CREATE_NODE':
self._apply_node_creation(improvement)
elif improvement['type'] == 'ADJUST_THRESHOLD':
self._apply_threshold_adjustment(improvement)
if self.dry_run:
print("\n💡 Mode simulation - Aucune modification appliquée")
print(" Relancez avec --apply pour appliquer les changements")
def _apply_prototype_update(self, improvement: Dict):
"""Appliquer une mise à jour de prototype."""
print(f" Node: {improvement['node_label']} (ID: {improvement['node_id']})")
print(f" Near misses: {improvement['near_miss_count']}")
print(f" Confiance moyenne: {improvement['avg_confidence']:.3f}")
if not self.dry_run:
# Charger tous les embeddings
embeddings = []
for emb_path in improvement['embeddings']:
if Path(emb_path).exists():
embeddings.append(np.load(emb_path))
if embeddings:
# Calculer le nouveau prototype (moyenne)
new_prototype = np.mean(embeddings, axis=0)
# Sauvegarder (à adapter selon votre structure)
prototype_path = self.workflows_dir / f"node_{improvement['node_id']}_prototype.npy"
np.save(prototype_path, new_prototype)
print(f" ✓ Prototype mis à jour: {prototype_path}")
else:
print(f" → Mettrait à jour le prototype avec {len(improvement['embeddings'])} embeddings")
def _apply_node_creation(self, improvement: Dict):
"""Appliquer une création de node."""
print(f" Fenêtre: {improvement['window_title']}")
print(f" Occurrences: {improvement['occurrence_count']}")
print(f" Confiance moyenne: {improvement['avg_confidence']:.3f}")
if not self.dry_run:
# Créer un nouveau node (à adapter selon votre structure)
node_id = f"node_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
node_dir = self.workflows_dir / node_id
node_dir.mkdir(parents=True, exist_ok=True)
# Copier les screenshots
for i, screenshot in enumerate(improvement['screenshots']):
if Path(screenshot).exists():
shutil.copy(screenshot, node_dir / f"example_{i}.png")
# Calculer et sauvegarder le prototype
embeddings = []
for emb_path in improvement['embeddings']:
if Path(emb_path).exists():
embeddings.append(np.load(emb_path))
if embeddings:
prototype = np.mean(embeddings, axis=0)
np.save(node_dir / "prototype.npy", prototype)
print(f" ✓ Node créé: {node_dir}")
else:
print(f" → Créerait un nouveau node avec {improvement['occurrence_count']} exemples")
def _apply_threshold_adjustment(self, improvement: Dict):
"""Appliquer un ajustement de seuil."""
print(f" Seuil actuel: {improvement['current_threshold']:.3f}")
print(f" Seuil recommandé: {improvement['recommended_threshold']:.3f}")
print(f" Raison: {improvement['reason']}")
if not self.dry_run:
# Mettre à jour la configuration (à adapter)
config_path = Path("config/matching_config.json")
if config_path.exists():
with open(config_path, 'r') as f:
config = json.load(f)
config['similarity_threshold'] = improvement['recommended_threshold']
with open(config_path, 'w') as f:
json.dump(config, f, indent=2)
print(f" ✓ Configuration mise à jour: {config_path}")
else:
print(f" → Mettrait à jour le seuil dans la configuration")
def print_summary(self):
"""Afficher un résumé des améliorations."""
print("\n" + "="*70)
print("RÉSUMÉ DES AMÉLIORATIONS PROPOSÉES")
print("="*70)
by_type = {}
for imp in self.improvements:
imp_type = imp['type']
if imp_type not in by_type:
by_type[imp_type] = []
by_type[imp_type].append(imp)
for imp_type, imps in by_type.items():
print(f"\n{imp_type}: {len(imps)}")
for imp in imps:
if imp_type == 'UPDATE_PROTOTYPE':
print(f"{imp['node_label']}: {imp['near_miss_count']} near misses")
elif imp_type == 'CREATE_NODE':
print(f"{imp['window_title']}: {imp['occurrence_count']} occurrences")
elif imp_type == 'ADJUST_THRESHOLD':
print(f"{imp['current_threshold']:.3f}{imp['recommended_threshold']:.3f}")
def main():
parser = argparse.ArgumentParser(
description="Amélioration automatique du système de matching"
)
parser.add_argument(
'--apply',
action='store_true',
help="Appliquer les améliorations (sinon mode simulation)"
)
parser.add_argument(
'--min-confidence',
type=float,
default=0.75,
help="Confiance minimum pour mise à jour (défaut: 0.75)"
)
args = parser.parse_args()
improver = MatchingAutoImprover(dry_run=not args.apply)
# Analyser
improvements = improver.analyze_and_improve(min_confidence=args.min_confidence)
if not improvements:
print("\n✅ Aucune amélioration nécessaire")
return 0
# Afficher le résumé
improver.print_summary()
# Appliquer
improver.apply_improvements()
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -1,26 +0,0 @@
# Capture d'Élément Cible VWB - Diagnostic
Auteur : Dom, Alice, Kiro - 09 janvier 2026
## Problème identifié
La capture d'élément cible ne fonctionne pas via l'API Flask mais fonctionne en direct.
## Fichiers clés
- visual_workflow_builder/backend/app_lightweight.py : Backend Flask principal
- visual_workflow_builder/frontend/src/components/VisualSelector/index.tsx : Composant frontend
- tests/integration/test_capture_element_cible_vwb_09jan2026.py : Test principal
- tests/integration/test_backend_vwb_simple_09jan2026.py : Test direct backend
## Tests à exécuter
1. Test direct : python3 tests/integration/test_backend_vwb_simple_09jan2026.py
2. Test complet : python3 tests/integration/test_capture_element_cible_vwb_09jan2026.py
## Environnement requis
- Environnement virtuel venv_v3 avec mss, pyautogui, torch, open_clip_torch
- Python 3.8+
- Écran disponible pour capture
## Symptômes
- ✅ Fonctions backend directes : OK
- ❌ Endpoints Flask /api/screen-capture : Erreur 500
- ✅ ScreenCapturer avec venv : OK
- ❌ ScreenCapturer via serveur Flask : Échec

View File

@@ -1,4 +0,0 @@
"""Screen capture module"""
from .screen_capturer import ScreenCapturer
__all__ = ['ScreenCapturer']

View File

@@ -1,480 +0,0 @@
"""
Screen Capture Module - Capture d'écran continue pour RPA Vision V3
Fonctionnalités:
- Capture unique ou continue
- Buffer circulaire pour historique
- Détection de changement d'écran
- Support multi-moniteur
- Optimisation mémoire
"""
import numpy as np
from typing import Optional, Dict, List, Callable, Tuple
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
import threading
import time
import logging
import hashlib
from PIL import Image
logger = logging.getLogger(__name__)
@dataclass
class CaptureFrame:
"""Un frame capturé avec métadonnées"""
image: np.ndarray
timestamp: datetime
frame_id: int
hash: str
window_info: Optional[Dict] = None
changed_from_previous: bool = True
@dataclass
class CaptureStats:
"""Statistiques de capture"""
total_captures: int = 0
captures_per_second: float = 0.0
unchanged_frames_skipped: int = 0
average_capture_time_ms: float = 0.0
buffer_size: int = 0
memory_usage_mb: float = 0.0
class ScreenCapturer:
"""
Capturer d'écran avancé avec mode continu.
Modes:
- Single: Capture unique à la demande
- Continuous: Capture en boucle avec callback
- Buffered: Maintient un historique des N derniers frames
Example:
>>> capturer = ScreenCapturer(buffer_size=10)
>>> # Capture unique
>>> frame = capturer.capture()
>>> # Mode continu
>>> capturer.start_continuous(callback=on_frame, interval_ms=500)
>>> # ... plus tard ...
>>> capturer.stop_continuous()
"""
def __init__(
self,
buffer_size: int = 10,
detect_changes: bool = True,
change_threshold: float = 0.02,
monitor_index: int = 1
):
"""
Initialiser le capturer.
Args:
buffer_size: Nombre de frames à garder en mémoire
detect_changes: Détecter si l'écran a changé
change_threshold: Seuil de changement (0-1)
monitor_index: Index du moniteur (1=principal)
"""
self.buffer_size = buffer_size
self.detect_changes = detect_changes
self.change_threshold = change_threshold
self.monitor_index = monitor_index
# Buffer circulaire
self._buffer: List[CaptureFrame] = []
self._frame_counter = 0
self._last_hash: Optional[str] = None
# Mode continu
self._continuous_running = False
self._continuous_thread: Optional[threading.Thread] = None
self._continuous_callback: Optional[Callable[[CaptureFrame], None]] = None
self._continuous_interval_ms = 500
self._lock = threading.Lock()
# Stats
self._stats = CaptureStats()
self._capture_times: List[float] = []
# Initialiser le backend de capture
self._init_capture_backend()
logger.info(f"ScreenCapturer initialized (buffer={buffer_size}, changes={detect_changes})")
def _init_capture_backend(self) -> None:
"""Initialiser le backend de capture (mss ou pyautogui)."""
self.sct = None
self.pyautogui = None
self.method = None
try:
import mss
self.sct = mss.mss()
self.method = "mss"
logger.info("Using mss for screen capture")
except ImportError:
try:
import pyautogui
self.pyautogui = pyautogui
self.method = "pyautogui"
logger.info("Using pyautogui for screen capture")
except ImportError:
raise ImportError("Neither mss nor pyautogui available for screen capture")
# =========================================================================
# Capture unique
# =========================================================================
def capture(self) -> Optional[np.ndarray]:
"""
Capture unique de l'écran.
Returns:
Screenshot as numpy array (H, W, 3) RGB ou None si erreur
"""
try:
start_time = time.time()
if self.method == "mss":
img = self._capture_mss()
else:
img = self._capture_pyautogui()
# Stats
capture_time = (time.time() - start_time) * 1000
self._capture_times.append(capture_time)
if len(self._capture_times) > 100:
self._capture_times.pop(0)
self._stats.total_captures += 1
self._stats.average_capture_time_ms = sum(self._capture_times) / len(self._capture_times)
return img
except Exception as e:
logger.error(f"Capture failed: {e}")
return None
def capture_frame(self) -> Optional[CaptureFrame]:
"""
Capture avec métadonnées complètes.
Returns:
CaptureFrame avec image, timestamp, hash, etc.
"""
img = self.capture()
return self._create_frame(img)
def _capture_frame_threaded(self, thread_sct) -> Optional[CaptureFrame]:
"""
Capture avec instance mss thread-local.
Args:
thread_sct: Instance mss créée dans le thread
Returns:
CaptureFrame ou None
"""
try:
start_time = time.time()
if self.method == "mss" and thread_sct:
monitor_idx = self.monitor_index if len(thread_sct.monitors) > self.monitor_index else 0
monitor = thread_sct.monitors[monitor_idx]
sct_img = thread_sct.grab(monitor)
img = np.array(sct_img)
img = img[:, :, :3][:, :, ::-1] # BGRA to RGB
else:
img = self._capture_pyautogui()
# Stats
capture_time = (time.time() - start_time) * 1000
self._capture_times.append(capture_time)
if len(self._capture_times) > 100:
self._capture_times.pop(0)
self._stats.total_captures += 1
self._stats.average_capture_time_ms = sum(self._capture_times) / len(self._capture_times)
return self._create_frame(img)
except Exception as e:
logger.error(f"Threaded capture failed: {e}")
return None
def _create_frame(self, img: Optional[np.ndarray]) -> Optional[CaptureFrame]:
"""Créer un CaptureFrame à partir d'une image."""
if img is None:
return None
# Calculer le hash pour détecter les changements
img_hash = self._compute_hash(img)
changed = True
if self.detect_changes and self._last_hash:
changed = img_hash != self._last_hash
if not changed:
self._stats.unchanged_frames_skipped += 1
self._last_hash = img_hash
self._frame_counter += 1
frame = CaptureFrame(
image=img,
timestamp=datetime.now(),
frame_id=self._frame_counter,
hash=img_hash,
window_info=self.get_active_window(),
changed_from_previous=changed
)
# Ajouter au buffer
self._add_to_buffer(frame)
return frame
def capture_screen(self) -> Optional[Image.Image]:
"""
Capture et retourne une PIL Image (compatibilité avec ExecutionLoop).
Returns:
PIL Image ou None
"""
img = self.capture()
if img is None:
return None
return Image.fromarray(img)
def _capture_mss(self) -> np.ndarray:
"""Capture using mss."""
monitor_idx = self.monitor_index if len(self.sct.monitors) > self.monitor_index else 0
monitor = self.sct.monitors[monitor_idx]
sct_img = self.sct.grab(monitor)
img = np.array(sct_img)
# Convert BGRA to RGB
img = img[:, :, :3][:, :, ::-1]
if img.size == 0 or img.shape[0] == 0 or img.shape[1] == 0:
raise ValueError("Captured image has invalid dimensions")
return img
def _capture_pyautogui(self) -> np.ndarray:
"""Capture using pyautogui."""
screenshot = self.pyautogui.screenshot()
img = np.array(screenshot)
if img.size == 0 or img.shape[0] == 0 or img.shape[1] == 0:
raise ValueError("Captured image has invalid dimensions")
return img
# =========================================================================
# Mode continu
# =========================================================================
def start_continuous(
self,
callback: Callable[[CaptureFrame], None],
interval_ms: int = 500,
skip_unchanged: bool = True
) -> bool:
"""
Démarrer la capture continue.
Args:
callback: Fonction appelée pour chaque frame
interval_ms: Intervalle entre captures (ms)
skip_unchanged: Ne pas appeler callback si écran inchangé
Returns:
True si démarré avec succès
"""
with self._lock:
if self._continuous_running:
logger.warning("Continuous capture already running")
return False
self._continuous_callback = callback
self._continuous_interval_ms = interval_ms
self._skip_unchanged = skip_unchanged
self._continuous_running = True
self._continuous_thread = threading.Thread(
target=self._continuous_loop,
daemon=True
)
self._continuous_thread.start()
logger.info(f"Started continuous capture (interval={interval_ms}ms)")
return True
def stop_continuous(self) -> None:
"""Arrêter la capture continue."""
with self._lock:
self._continuous_running = False
if self._continuous_thread:
self._continuous_thread.join(timeout=2.0)
self._continuous_thread = None
logger.info("Stopped continuous capture")
def is_continuous_running(self) -> bool:
"""Vérifier si la capture continue est active."""
return self._continuous_running
def _continuous_loop(self) -> None:
"""Boucle de capture continue (thread)."""
last_capture_time = 0
captures_in_second = 0
second_start = time.time()
# Créer une nouvelle instance mss pour ce thread (requis pour X11)
thread_sct = None
if self.method == "mss":
import mss
thread_sct = mss.mss()
while self._continuous_running:
try:
# Capturer avec l'instance thread-local
frame = self._capture_frame_threaded(thread_sct)
if frame:
# Calculer FPS
captures_in_second += 1
if time.time() - second_start >= 1.0:
self._stats.captures_per_second = captures_in_second
captures_in_second = 0
second_start = time.time()
# Appeler callback si changement ou si on ne skip pas
if self._continuous_callback:
if frame.changed_from_previous or not self._skip_unchanged:
try:
self._continuous_callback(frame)
except Exception as e:
logger.error(f"Callback error: {e}")
# Attendre l'intervalle
elapsed = (time.time() - last_capture_time) * 1000
sleep_time = max(0, self._continuous_interval_ms - elapsed) / 1000.0
if sleep_time > 0:
time.sleep(sleep_time)
last_capture_time = time.time()
except Exception as e:
logger.error(f"Continuous capture error: {e}")
time.sleep(0.1)
# Cleanup thread-local mss
if thread_sct:
try:
thread_sct.close()
except Exception:
pass
# =========================================================================
# Buffer et historique
# =========================================================================
def _add_to_buffer(self, frame: CaptureFrame) -> None:
"""Ajouter un frame au buffer circulaire."""
with self._lock:
self._buffer.append(frame)
if len(self._buffer) > self.buffer_size:
self._buffer.pop(0)
self._stats.buffer_size = len(self._buffer)
# Calculer utilisation mémoire
if self._buffer:
frame_size = self._buffer[0].image.nbytes / (1024 * 1024)
self._stats.memory_usage_mb = frame_size * len(self._buffer)
def get_buffer(self) -> List[CaptureFrame]:
"""Obtenir une copie du buffer."""
with self._lock:
return list(self._buffer)
def get_last_frame(self) -> Optional[CaptureFrame]:
"""Obtenir le dernier frame capturé."""
with self._lock:
return self._buffer[-1] if self._buffer else None
def get_frame_by_id(self, frame_id: int) -> Optional[CaptureFrame]:
"""Obtenir un frame par son ID."""
with self._lock:
for frame in self._buffer:
if frame.frame_id == frame_id:
return frame
return None
def clear_buffer(self) -> None:
"""Vider le buffer."""
with self._lock:
self._buffer.clear()
self._stats.buffer_size = 0
# =========================================================================
# Utilitaires
# =========================================================================
def _compute_hash(self, img: np.ndarray) -> str:
"""Calculer un hash rapide de l'image pour détecter les changements."""
# Sous-échantillonner pour un hash rapide
small = img[::20, ::20, :].tobytes()
return hashlib.md5(small).hexdigest()
def get_active_window(self) -> Optional[Dict]:
"""Obtenir les infos de la fenêtre active."""
try:
import pygetwindow as gw
active = gw.getActiveWindow()
if active:
return {
'title': active.title,
'x': active.left,
'y': active.top,
'width': active.width,
'height': active.height,
'app': getattr(active, '_app', 'unknown')
}
except Exception as e:
logger.debug(f"Could not get active window: {e}")
return None
def get_screen_resolution(self) -> Tuple[int, int]:
"""Obtenir la résolution de l'écran."""
if self.method == "mss":
monitor = self.sct.monitors[self.monitor_index]
return (monitor['width'], monitor['height'])
else:
size = self.pyautogui.size()
return (size.width, size.height)
def get_stats(self) -> CaptureStats:
"""Obtenir les statistiques de capture."""
return self._stats
def save_frame(self, frame: CaptureFrame, path: str) -> bool:
"""Sauvegarder un frame sur disque."""
try:
img = Image.fromarray(frame.image)
img.save(path)
return True
except Exception as e:
logger.error(f"Failed to save frame: {e}")
return False
def __del__(self):
"""Cleanup."""
self.stop_continuous()
if self.sct:
try:
self.sct.close()
except (AttributeError, RuntimeError, OSError):
pass

View File

@@ -1,96 +0,0 @@
"""
Embedding Module - Fusion Multi-Modale et Gestion FAISS
Ce module gère la fusion d'embeddings multi-modaux et l'indexation FAISS
pour la recherche de similarité rapide.
"""
from .fusion_engine import (
FusionEngine,
FusionConfig,
create_default_fusion_engine,
normalize_vector,
validate_weights
)
from .faiss_manager import (
FAISSManager,
SearchResult,
create_flat_index,
create_ivf_index
)
from .similarity import (
cosine_similarity,
euclidean_distance,
manhattan_distance,
dot_product,
normalize_l2,
normalize_l1,
angular_distance,
jaccard_similarity,
hamming_distance,
batch_cosine_similarity,
pairwise_cosine_similarity,
similarity_to_distance,
distance_to_similarity,
is_normalized,
compute_centroid,
compute_variance
)
from .state_embedding_builder import (
StateEmbeddingBuilder,
create_builder,
build_from_screen_state
)
from .base_embedder import EmbedderBase
from .clip_embedder import (
CLIPEmbedder,
create_clip_embedder,
get_default_embedder
)
from .embedding_cache import (
EmbeddingCache,
PrototypeCache
)
__all__ = [
'FusionEngine',
'FusionConfig',
'create_default_fusion_engine',
'normalize_vector',
'validate_weights',
'FAISSManager',
'SearchResult',
'create_flat_index',
'create_ivf_index',
'cosine_similarity',
'euclidean_distance',
'manhattan_distance',
'dot_product',
'normalize_l2',
'normalize_l1',
'angular_distance',
'jaccard_similarity',
'hamming_distance',
'batch_cosine_similarity',
'pairwise_cosine_similarity',
'similarity_to_distance',
'distance_to_similarity',
'is_normalized',
'compute_centroid',
'compute_variance',
'StateEmbeddingBuilder',
'create_builder',
'build_from_screen_state',
'EmbedderBase',
'CLIPEmbedder',
'create_clip_embedder',
'get_default_embedder',
'EmbeddingCache',
'PrototypeCache'
]

View File

@@ -1,136 +0,0 @@
"""
Abstract base class for embedding models.
This module defines the interface that all embedding models must implement,
ensuring consistency across different model implementations (CLIP, etc.).
"""
from abc import ABC, abstractmethod
from typing import List
from PIL import Image
import numpy as np
class EmbedderBase(ABC):
"""
Abstract base class for image and text embedding models.
All embedding models must implement this interface to ensure
compatibility with the state embedding system.
"""
@abstractmethod
def embed_image(self, image: Image.Image) -> np.ndarray:
"""
Generate an embedding vector for a single image.
Args:
image: PIL Image to embed
Returns:
np.ndarray: Normalized embedding vector of shape (dimension,)
The vector should be L2-normalized for cosine similarity
Raises:
ValueError: If image is invalid or cannot be processed
RuntimeError: If model inference fails
"""
pass
@abstractmethod
def embed_text(self, text: str) -> np.ndarray:
"""
Generate an embedding vector for text.
Args:
text: Text string to embed
Returns:
np.ndarray: Normalized embedding vector of shape (dimension,)
The vector should be L2-normalized for cosine similarity
Raises:
ValueError: If text is invalid
RuntimeError: If model inference fails
"""
pass
@abstractmethod
def get_dimension(self) -> int:
"""
Get the dimensionality of embeddings produced by this model.
Returns:
int: Embedding dimension (e.g., 512 for CLIP ViT-B/32)
"""
pass
@abstractmethod
def get_model_name(self) -> str:
"""
Get a unique identifier for this model.
Returns:
str: Model name (e.g., "clip-vit-b32")
"""
pass
def embed_image_batch(self, images: List[Image.Image]) -> np.ndarray:
"""
Generate embeddings for multiple images.
Default implementation processes images one by one.
Subclasses can override this for optimized batch processing.
Args:
images: List of PIL Images to embed
Returns:
np.ndarray: Array of embeddings with shape (len(images), dimension)
Each row is a normalized embedding vector
Raises:
ValueError: If any image is invalid
RuntimeError: If model inference fails
"""
if not images:
return np.array([]).reshape(0, self.get_dimension())
embeddings = []
for img in images:
embedding = self.embed_image(img)
embeddings.append(embedding)
return np.array(embeddings)
def embed_text_batch(self, texts: List[str]) -> np.ndarray:
"""
Generate embeddings for multiple texts.
Default implementation processes texts one by one.
Subclasses can override this for optimized batch processing.
Args:
texts: List of text strings to embed
Returns:
np.ndarray: Array of embeddings with shape (len(texts), dimension)
Each row is a normalized embedding vector
Raises:
ValueError: If any text is invalid
RuntimeError: If model inference fails
"""
if not texts:
return np.array([]).reshape(0, self.get_dimension())
embeddings = []
for text in texts:
embedding = self.embed_text(text)
embeddings.append(embedding)
return np.array(embeddings)
def __repr__(self) -> str:
"""String representation of the embedder."""
return f"{self.__class__.__name__}(model={self.get_model_name()}, dim={self.get_dimension()})"

View File

@@ -1,292 +0,0 @@
"""
CLIP-based embedder implementation for RPA Vision V3.
This module provides a wrapper around OpenCLIP for generating image and text embeddings
using the CLIP (Contrastive Language-Image Pre-training) model.
"""
import torch
import numpy as np
from PIL import Image
from typing import List, Optional
import logging
try:
import open_clip
except ImportError:
open_clip = None
from .base_embedder import EmbedderBase
logger = logging.getLogger(__name__)
class CLIPEmbedder(EmbedderBase):
"""
CLIP-based image and text embedder using OpenCLIP.
This embedder uses the ViT-B/32 architecture by default, which produces
512-dimensional embeddings. It automatically handles GPU/CPU device selection.
The embeddings are L2-normalized for cosine similarity calculations.
"""
def __init__(
self,
model_name: str = "ViT-B-32",
pretrained: str = "openai",
device: Optional[str] = None
):
"""
Initialize the CLIP embedder.
Args:
model_name: CLIP model architecture (default: ViT-B-32)
Options: ViT-B-32, ViT-B-16, ViT-L-14, etc.
pretrained: Pretrained weights to use (default: openai)
device: Device to use ('cuda', 'cpu', or None for auto-detect)
Defaults to CPU to save GPU memory for VLM models
Raises:
ImportError: If open_clip is not installed
RuntimeError: If model loading fails
"""
if open_clip is None:
raise ImportError(
"OpenCLIP is not installed. "
"Install it with: pip install open-clip-torch"
)
# Default to CPU to save GPU for vision models (Qwen3-VL, etc.)
if device is None:
device = "cpu"
self.model_name = model_name
self.pretrained = pretrained
self.device = device
self._embedding_dim = None
# Load model
try:
logger.info(f"Loading CLIP model: {model_name} ({pretrained}) on {device}...")
self.model, _, self.preprocess = open_clip.create_model_and_transforms(
model_name,
pretrained=pretrained,
device=device
)
self.model.eval()
# Get tokenizer for text
self.tokenizer = open_clip.get_tokenizer(model_name)
# Determine embedding dimension
with torch.no_grad():
dummy_image = torch.zeros(1, 3, 224, 224).to(self.device)
dummy_embedding = self.model.encode_image(dummy_image)
self._embedding_dim = dummy_embedding.shape[-1]
logger.info(
f"✓ CLIP embedder loaded: {model_name} on {device}, "
f"dimension={self._embedding_dim}"
)
except Exception as e:
raise RuntimeError(f"Failed to load CLIP model: {e}")
def embed_image(self, image: Image.Image) -> np.ndarray:
"""
Generate embedding for a single image.
Args:
image: PIL Image to embed
Returns:
np.ndarray: Normalized embedding vector of shape (dimension,)
Raises:
ValueError: If image is invalid
RuntimeError: If embedding generation fails
"""
if not isinstance(image, Image.Image):
raise ValueError("Input must be a PIL Image")
try:
# Preprocess image
image_tensor = self.preprocess(image).unsqueeze(0).to(self.device)
# Generate embedding
with torch.no_grad():
embedding = self.model.encode_image(image_tensor)
# L2 normalize for cosine similarity
embedding = embedding / embedding.norm(dim=-1, keepdim=True)
return embedding.cpu().numpy().flatten()
except Exception as e:
raise RuntimeError(f"Failed to generate image embedding: {e}")
def embed_text(self, text: str) -> np.ndarray:
"""
Generate embedding for text.
Args:
text: Text string to embed
Returns:
np.ndarray: Normalized embedding vector of shape (dimension,)
Raises:
ValueError: If text is invalid
RuntimeError: If embedding generation fails
"""
if not isinstance(text, str):
raise ValueError("Input must be a string")
if not text.strip():
# Return zero vector for empty text
return np.zeros(self.get_dimension(), dtype=np.float32)
try:
# Tokenize text
text_tokens = self.tokenizer([text]).to(self.device)
# Generate embedding
with torch.no_grad():
embedding = self.model.encode_text(text_tokens)
# L2 normalize for cosine similarity
embedding = embedding / embedding.norm(dim=-1, keepdim=True)
return embedding.cpu().numpy().flatten()
except Exception as e:
raise RuntimeError(f"Failed to generate text embedding: {e}")
def embed_image_batch(self, images: List[Image.Image]) -> np.ndarray:
"""
Generate embeddings for multiple images (optimized batch processing).
Args:
images: List of PIL Images to embed
Returns:
np.ndarray: Array of embeddings with shape (len(images), dimension)
Raises:
ValueError: If any image is invalid
RuntimeError: If embedding generation fails
"""
if not images:
return np.array([]).reshape(0, self.get_dimension())
# Validate all images
for i, img in enumerate(images):
if not isinstance(img, Image.Image):
raise ValueError(f"Image at index {i} is not a PIL Image")
try:
# Preprocess all images
image_tensors = torch.stack([
self.preprocess(img) for img in images
]).to(self.device)
# Generate embeddings in batch
with torch.no_grad():
embeddings = self.model.encode_image(image_tensors)
# L2 normalize for cosine similarity
embeddings = embeddings / embeddings.norm(dim=-1, keepdim=True)
return embeddings.cpu().numpy()
except Exception as e:
raise RuntimeError(f"Failed to generate batch image embeddings: {e}")
def embed_text_batch(self, texts: List[str]) -> np.ndarray:
"""
Generate embeddings for multiple texts (optimized batch processing).
Args:
texts: List of text strings to embed
Returns:
np.ndarray: Array of embeddings with shape (len(texts), dimension)
Raises:
ValueError: If any text is invalid
RuntimeError: If embedding generation fails
"""
if not texts:
return np.array([]).reshape(0, self.get_dimension())
# Validate all texts
for i, text in enumerate(texts):
if not isinstance(text, str):
raise ValueError(f"Text at index {i} is not a string")
try:
# Handle empty texts
processed_texts = [text if text.strip() else " " for text in texts]
# Tokenize all texts
text_tokens = self.tokenizer(processed_texts).to(self.device)
# Generate embeddings in batch
with torch.no_grad():
embeddings = self.model.encode_text(text_tokens)
# L2 normalize for cosine similarity
embeddings = embeddings / embeddings.norm(dim=-1, keepdim=True)
return embeddings.cpu().numpy()
except Exception as e:
raise RuntimeError(f"Failed to generate batch text embeddings: {e}")
def get_dimension(self) -> int:
"""
Get the dimensionality of embeddings.
Returns:
int: Embedding dimension (512 for ViT-B/32)
"""
return self._embedding_dim
def get_model_name(self) -> str:
"""
Get model identifier.
Returns:
str: Model name (e.g., "clip-vit-b32")
"""
return f"clip-{self.model_name.lower().replace('/', '-')}"
# ============================================================================
# Factory functions
# ============================================================================
def create_clip_embedder(
model_name: str = "ViT-B-32",
device: Optional[str] = None
) -> CLIPEmbedder:
"""
Create a CLIP embedder with default configuration.
Args:
model_name: CLIP model architecture (default: ViT-B-32)
device: Device to use (default: CPU)
Returns:
CLIPEmbedder: Configured CLIP embedder
"""
return CLIPEmbedder(model_name=model_name, device=device)
def get_default_embedder() -> CLIPEmbedder:
"""
Get the default CLIP embedder (ViT-B/32 on CPU).
Returns:
CLIPEmbedder: Default embedder
"""
return CLIPEmbedder()

View File

@@ -1,284 +0,0 @@
"""
Embedding Cache - Cache LRU pour embeddings
Implémente un cache LRU (Least Recently Used) pour stocker
les embeddings en mémoire et éviter les recalculs coûteux.
"""
import logging
from typing import Optional, Dict, Any
from collections import OrderedDict
import numpy as np
from datetime import datetime
logger = logging.getLogger(__name__)
class EmbeddingCache:
"""
Cache LRU pour embeddings.
Stocke les embeddings les plus récemment utilisés en mémoire
pour éviter les recalculs et chargements depuis disque.
Features:
- LRU eviction policy
- Taille maximale configurable
- Statistiques de cache (hits/misses)
- Invalidation sélective
"""
def __init__(self, max_size: int = 1000, max_memory_mb: float = 500.0):
"""
Initialiser le cache.
Args:
max_size: Nombre maximum d'embeddings à garder en cache
max_memory_mb: Mémoire maximale en MB (approximatif)
"""
self.max_size = max_size
self.max_memory_mb = max_memory_mb
self.cache: OrderedDict[str, np.ndarray] = OrderedDict()
self.metadata: Dict[str, Dict[str, Any]] = {}
# Statistiques
self.hits = 0
self.misses = 0
self.evictions = 0
logger.info(
f"EmbeddingCache initialized: max_size={max_size}, "
f"max_memory_mb={max_memory_mb:.1f}"
)
def get(self, key: str) -> Optional[np.ndarray]:
"""
Récupérer un embedding du cache.
Args:
key: Clé de l'embedding (embedding_id)
Returns:
Vecteur numpy si trouvé, None sinon
"""
if key in self.cache:
# Déplacer à la fin (most recently used)
self.cache.move_to_end(key)
self.hits += 1
logger.debug(f"Cache HIT: {key}")
return self.cache[key]
self.misses += 1
logger.debug(f"Cache MISS: {key}")
return None
def put(
self,
key: str,
vector: np.ndarray,
metadata: Optional[Dict[str, Any]] = None
):
"""
Ajouter un embedding au cache.
Args:
key: Clé de l'embedding
vector: Vecteur numpy
metadata: Métadonnées optionnelles
"""
# Si déjà présent, mettre à jour et déplacer à la fin
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = vector
if metadata:
self.metadata[key] = metadata
return
# Vérifier si on doit évict
if len(self.cache) >= self.max_size:
self._evict_oldest()
# Ajouter le nouvel embedding
self.cache[key] = vector
if metadata:
self.metadata[key] = metadata
logger.debug(f"Cache PUT: {key} (size: {len(self.cache)})")
def _evict_oldest(self):
"""Évict l'embedding le moins récemment utilisé."""
if not self.cache:
return
# Retirer le premier élément (oldest)
oldest_key, _ = self.cache.popitem(last=False)
self.metadata.pop(oldest_key, None)
self.evictions += 1
logger.debug(f"Cache EVICT: {oldest_key} (evictions: {self.evictions})")
def invalidate(self, key: str):
"""
Invalider un embedding spécifique.
Args:
key: Clé de l'embedding à invalider
"""
if key in self.cache:
del self.cache[key]
self.metadata.pop(key, None)
logger.debug(f"Cache INVALIDATE: {key}")
def invalidate_pattern(self, pattern: str):
"""
Invalider tous les embeddings dont la clé contient le pattern.
Args:
pattern: Pattern à rechercher dans les clés
"""
keys_to_remove = [k for k in self.cache.keys() if pattern in k]
for key in keys_to_remove:
del self.cache[key]
self.metadata.pop(key, None)
if keys_to_remove:
logger.info(f"Cache INVALIDATE PATTERN '{pattern}': {len(keys_to_remove)} entries")
def clear(self):
"""Vider complètement le cache."""
size_before = len(self.cache)
self.cache.clear()
self.metadata.clear()
logger.info(f"Cache CLEAR: {size_before} entries removed")
def get_stats(self) -> Dict[str, Any]:
"""
Obtenir les statistiques du cache.
Returns:
Dict avec statistiques
"""
total_requests = self.hits + self.misses
hit_rate = self.hits / total_requests if total_requests > 0 else 0.0
# Estimer la mémoire utilisée
memory_mb = 0.0
for vector in self.cache.values():
# Taille en bytes = nombre d'éléments * taille d'un float32
memory_mb += vector.nbytes / (1024 * 1024)
return {
"size": len(self.cache),
"max_size": self.max_size,
"hits": self.hits,
"misses": self.misses,
"evictions": self.evictions,
"hit_rate": hit_rate,
"memory_mb": memory_mb,
"max_memory_mb": self.max_memory_mb,
"memory_usage_pct": (memory_mb / self.max_memory_mb * 100) if self.max_memory_mb > 0 else 0.0
}
def __len__(self) -> int:
"""Retourne le nombre d'embeddings en cache."""
return len(self.cache)
def __contains__(self, key: str) -> bool:
"""Vérifie si une clé est dans le cache."""
return key in self.cache
class PrototypeCache:
"""
Cache spécialisé pour les prototypes de WorkflowNodes.
Les prototypes sont utilisés fréquemment pour le matching,
donc on les garde en cache avec une politique différente.
"""
def __init__(self, max_size: int = 100):
"""
Initialiser le cache de prototypes.
Args:
max_size: Nombre maximum de prototypes à garder
"""
self.max_size = max_size
self.cache: Dict[str, np.ndarray] = {}
self.access_count: Dict[str, int] = {}
self.last_access: Dict[str, datetime] = {}
logger.info(f"PrototypeCache initialized: max_size={max_size}")
def get(self, node_id: str) -> Optional[np.ndarray]:
"""
Récupérer un prototype du cache.
Args:
node_id: ID du WorkflowNode
Returns:
Vecteur prototype si trouvé, None sinon
"""
if node_id in self.cache:
self.access_count[node_id] = self.access_count.get(node_id, 0) + 1
self.last_access[node_id] = datetime.now()
return self.cache[node_id]
return None
def put(self, node_id: str, prototype: np.ndarray):
"""
Ajouter un prototype au cache.
Args:
node_id: ID du WorkflowNode
prototype: Vecteur prototype
"""
# Si cache plein, évict le moins utilisé
if len(self.cache) >= self.max_size and node_id not in self.cache:
self._evict_least_used()
self.cache[node_id] = prototype
self.access_count[node_id] = self.access_count.get(node_id, 0) + 1
self.last_access[node_id] = datetime.now()
def _evict_least_used(self):
"""Évict le prototype le moins utilisé."""
if not self.cache:
return
# Trouver le moins utilisé
least_used = min(self.access_count.items(), key=lambda x: x[1])
node_id = least_used[0]
del self.cache[node_id]
del self.access_count[node_id]
del self.last_access[node_id]
logger.debug(f"PrototypeCache EVICT: {node_id}")
def invalidate(self, node_id: str):
"""Invalider un prototype spécifique."""
if node_id in self.cache:
del self.cache[node_id]
self.access_count.pop(node_id, None)
self.last_access.pop(node_id, None)
def clear(self):
"""Vider le cache."""
self.cache.clear()
self.access_count.clear()
self.last_access.clear()
def get_stats(self) -> Dict[str, Any]:
"""Obtenir les statistiques du cache."""
total_accesses = sum(self.access_count.values())
avg_accesses = total_accesses / len(self.cache) if self.cache else 0.0
return {
"size": len(self.cache),
"max_size": self.max_size,
"total_accesses": total_accesses,
"avg_accesses_per_prototype": avg_accesses
}

View File

@@ -1,692 +0,0 @@
"""
FAISSManager - Gestion d'Index FAISS pour Recherche de Similarité
Gère l'indexation et la recherche rapide d'embeddings avec FAISS.
Supporte sauvegarde/chargement d'index et métadonnées.
"""
import logging
from typing import List, Dict, Optional, Tuple, Any
from pathlib import Path
from dataclasses import dataclass
import numpy as np
import json
import pickle
logger = logging.getLogger(__name__)
try:
import faiss
FAISS_AVAILABLE = True
except ImportError:
FAISS_AVAILABLE = False
logger.warning("FAISS not installed. Install with: pip install faiss-cpu")
@dataclass
class SearchResult:
"""Résultat d'une recherche de similarité"""
embedding_id: str
similarity: float # Similarité cosinus
distance: float # Distance L2
metadata: Dict[str, Any]
class FAISSManager:
"""
Gestionnaire d'index FAISS
Gère l'ajout, la recherche et la persistence d'embeddings avec FAISS.
Maintient un mapping entre IDs FAISS et métadonnées.
Features d'optimisation:
- Migration automatique Flat → IVF pour >10k embeddings
- Entraînement automatique de l'index IVF
- Support GPU si disponible
- Optimisation périodique de l'index
"""
def __init__(self,
dimensions: int,
index_type: str = "Flat",
metric: str = "cosine",
nlist: Optional[int] = None,
nprobe: int = 8,
use_gpu: bool = False,
auto_optimize: bool = True):
"""
Initialiser le gestionnaire FAISS
Args:
dimensions: Nombre de dimensions des vecteurs
index_type: Type d'index FAISS ("Flat", "IVF", "HNSW")
metric: Métrique de distance ("cosine", "l2", "ip")
nlist: Nombre de clusters pour IVF (auto si None)
nprobe: Nombre de clusters à visiter lors de la recherche IVF
use_gpu: Utiliser GPU si disponible
auto_optimize: Migrer automatiquement vers IVF si >10k embeddings
Raises:
ImportError: Si FAISS n'est pas installé
"""
if not FAISS_AVAILABLE:
raise ImportError(
"FAISS is required but not installed. "
"Install with: pip install faiss-cpu"
)
self.dimensions = dimensions
self.index_type = index_type
self.metric = metric
self.nlist = nlist
self.nprobe = nprobe
self.use_gpu = use_gpu
self.auto_optimize = auto_optimize
# Mapping ID FAISS -> métadonnées
self.metadata_store: Dict[int, Dict[str, Any]] = {}
# Compteur pour IDs FAISS
self.next_id = 0
# Vecteurs pour entraînement IVF (si nécessaire)
self.training_vectors: List[np.ndarray] = []
self.is_trained = (index_type == "Flat") # Flat n'a pas besoin d'entraînement
# Seuil pour migration automatique
self.migration_threshold = 10000
# GPU resources
self.gpu_resources = None
if use_gpu:
self._setup_gpu()
# Créer l'index FAISS (après avoir initialisé tous les attributs)
self.index = self._create_index()
def _setup_gpu(self):
"""Configurer les ressources GPU si disponibles"""
try:
# Vérifier si GPU est disponible
ngpus = faiss.get_num_gpus()
if ngpus > 0:
self.gpu_resources = faiss.StandardGpuResources()
logger.info(f"FAISS GPU enabled: {ngpus} GPU(s) available")
else:
logger.warning("FAISS GPU requested but no GPU available, using CPU")
self.use_gpu = False
except Exception as e:
logger.warning(f"FAISS GPU setup failed: {e}, using CPU")
self.use_gpu = False
def _calculate_nlist(self, n_vectors: int) -> int:
"""
Calculer le nombre optimal de clusters pour IVF
Règle empirique: nlist = sqrt(n_vectors)
Minimum: 100, Maximum: 65536
Args:
n_vectors: Nombre de vecteurs dans l'index
Returns:
Nombre optimal de clusters
"""
if self.nlist is not None:
return self.nlist
# Règle empirique
nlist = int(np.sqrt(n_vectors))
# Contraintes
nlist = max(100, min(nlist, 65536))
return nlist
def _create_index(self) -> 'faiss.Index':
"""Créer un index FAISS selon la configuration"""
if self.metric == "cosine":
# Pour cosine similarity, normaliser et utiliser inner product
if self.index_type == "Flat":
index = faiss.IndexFlatIP(self.dimensions)
elif self.index_type == "IVF":
# Calculer nlist optimal
nlist = self._calculate_nlist(max(1000, self.migration_threshold))
quantizer = faiss.IndexFlatIP(self.dimensions)
index = faiss.IndexIVFFlat(quantizer, self.dimensions, nlist)
# Configurer nprobe
index.nprobe = self.nprobe
# Activer DirectMap pour permettre reconstruct()
index.make_direct_map()
elif self.index_type == "HNSW":
index = faiss.IndexHNSWFlat(self.dimensions, 32)
else:
raise ValueError(f"Unknown index type: {self.index_type}")
elif self.metric == "l2":
if self.index_type == "Flat":
index = faiss.IndexFlatL2(self.dimensions)
elif self.index_type == "IVF":
# Calculer nlist optimal
nlist = self._calculate_nlist(max(1000, self.migration_threshold))
quantizer = faiss.IndexFlatL2(self.dimensions)
index = faiss.IndexIVFFlat(quantizer, self.dimensions, nlist)
# Configurer nprobe
index.nprobe = self.nprobe
# Activer DirectMap pour permettre reconstruct()
index.make_direct_map()
elif self.index_type == "HNSW":
index = faiss.IndexHNSWFlat(self.dimensions, 32)
else:
raise ValueError(f"Unknown index type: {self.index_type}")
elif self.metric == "ip": # Inner product
if self.index_type == "Flat":
index = faiss.IndexFlatIP(self.dimensions)
else:
raise ValueError(f"Inner product only supports Flat index")
else:
raise ValueError(f"Unknown metric: {self.metric}")
# Migrer vers GPU si demandé
if self.use_gpu and self.gpu_resources is not None:
try:
index = faiss.index_cpu_to_gpu(self.gpu_resources, 0, index)
except Exception as e:
logger.warning(f"Failed to move index to GPU: {e}, using CPU")
return index
def add_embedding(self,
embedding_id: str,
vector: np.ndarray,
metadata: Optional[Dict[str, Any]] = None) -> int:
"""
Ajouter un embedding à l'index
Args:
embedding_id: ID unique de l'embedding
vector: Vecteur d'embedding (dimensions doivent correspondre)
metadata: Métadonnées associées (optionnel)
Returns:
ID FAISS assigné
Raises:
ValueError: Si dimensions ne correspondent pas
"""
if vector.shape[0] != self.dimensions:
raise ValueError(
f"Vector dimensions mismatch: expected {self.dimensions}, "
f"got {vector.shape[0]}"
)
# Convertir en float32 d'abord
vector_float32 = vector.astype(np.float32)
# Normaliser si métrique cosine
if self.metric == "cosine":
norm = np.linalg.norm(vector_float32)
if norm > 0:
vector_float32 = vector_float32 / norm
# Reshape pour FAISS
vector_reshaped = vector_float32.reshape(1, -1)
# Pour IVF, stocker vecteurs pour entraînement si pas encore entraîné
if self.index_type == "IVF" and not self.is_trained:
self.training_vectors.append(vector_float32) # Stocker le vecteur normalisé
# Entraîner si on a assez de vecteurs
if len(self.training_vectors) >= 100:
self._train_ivf_index()
# Les vecteurs d'entraînement ont déjà été ajoutés dans _train_ivf_index
# Ne pas ajouter à nouveau
elif self.is_trained:
# Ajouter à l'index (seulement si entraîné pour IVF ou si Flat)
self.index.add(vector_reshaped)
# Stocker métadonnées
faiss_id = self.next_id
self.metadata_store[faiss_id] = {
"embedding_id": embedding_id,
"metadata": metadata or {}
}
self.next_id += 1
# Vérifier si migration automatique nécessaire
if self.auto_optimize and self.index_type == "Flat":
if self.index.ntotal >= self.migration_threshold:
self._migrate_to_ivf()
return faiss_id
def _train_ivf_index(self):
"""Entraîner l'index IVF avec les vecteurs collectés"""
if self.is_trained or self.index_type != "IVF":
return
if len(self.training_vectors) < 100:
logger.warning(f" Training IVF with only {len(self.training_vectors)} vectors")
# Convertir en array numpy
training_data = np.array(self.training_vectors, dtype=np.float32)
logger.info(f"Training IVF index with {len(self.training_vectors)} vectors...")
# Entraîner l'index
self.index.train(training_data)
self.is_trained = True
# Ajouter tous les vecteurs d'entraînement à l'index
self.index.add(training_data)
# Libérer mémoire
self.training_vectors.clear()
logger.info(f"IVF index trained successfully with nlist={self.index.nlist}")
def _migrate_to_ivf(self):
"""
Migrer automatiquement de Flat vers IVF
Appelé automatiquement quand l'index Flat dépasse le seuil.
"""
if self.index_type != "Flat":
return
logger.info(f"Migrating from Flat to IVF (current size: {self.index.ntotal})...")
# Extraire tous les vecteurs de l'index Flat
n_vectors = self.index.ntotal
vectors = np.zeros((n_vectors, self.dimensions), dtype=np.float32)
for i in range(n_vectors):
vectors[i] = self.index.reconstruct(int(i))
# Calculer nlist optimal
nlist = self._calculate_nlist(n_vectors)
# Créer nouvel index IVF
if self.metric == "cosine":
quantizer = faiss.IndexFlatIP(self.dimensions)
new_index = faiss.IndexIVFFlat(quantizer, self.dimensions, nlist)
else: # l2
quantizer = faiss.IndexFlatL2(self.dimensions)
new_index = faiss.IndexIVFFlat(quantizer, self.dimensions, nlist)
new_index.nprobe = self.nprobe
new_index.make_direct_map() # Activer DirectMap
# Entraîner avec tous les vecteurs
new_index.train(vectors)
# Ajouter tous les vecteurs
new_index.add(vectors)
# Remplacer l'index
self.index = new_index
self.index_type = "IVF"
self.is_trained = True
logger.info(f"Migration complete: IVF index with nlist={nlist}, nprobe={self.nprobe}")
def optimize_index(self):
"""
Optimiser l'index périodiquement
Pour IVF: Recalculer nlist optimal et réentraîner si nécessaire
"""
if self.index_type != "IVF" or not self.is_trained:
return
n_vectors = self.index.ntotal
if n_vectors < 100:
return
# Calculer nlist optimal pour la taille actuelle
optimal_nlist = self._calculate_nlist(n_vectors)
# Si nlist actuel est très différent, reconstruire
current_nlist = self.index.nlist
if abs(optimal_nlist - current_nlist) / current_nlist > 0.5:
logger.info(f"Optimizing IVF index: {current_nlist}{optimal_nlist} clusters")
# Extraire tous les vecteurs
vectors = np.zeros((n_vectors, self.dimensions), dtype=np.float32)
for i in range(n_vectors):
vectors[i] = self.index.reconstruct(int(i))
# Créer nouvel index avec nlist optimal
if self.metric == "cosine":
quantizer = faiss.IndexFlatIP(self.dimensions)
new_index = faiss.IndexIVFFlat(quantizer, self.dimensions, optimal_nlist)
else:
quantizer = faiss.IndexFlatL2(self.dimensions)
new_index = faiss.IndexIVFFlat(quantizer, self.dimensions, optimal_nlist)
new_index.nprobe = self.nprobe
new_index.make_direct_map() # Activer DirectMap
# Entraîner et ajouter
new_index.train(vectors)
new_index.add(vectors)
# Remplacer
self.index = new_index
logger.info("Index optimized successfully")
def search_similar(self,
query_vector: np.ndarray,
k: int = 5,
min_similarity: Optional[float] = None) -> List[SearchResult]:
"""
Rechercher les k embeddings les plus similaires
Args:
query_vector: Vecteur de requête
k: Nombre de résultats à retourner
min_similarity: Similarité minimale (optionnel, pour cosine)
Returns:
Liste de SearchResult triés par similarité décroissante
Raises:
ValueError: Si dimensions ne correspondent pas
"""
if query_vector.shape[0] != self.dimensions:
raise ValueError(
f"Query vector dimensions mismatch: expected {self.dimensions}, "
f"got {query_vector.shape[0]}"
)
if self.index.ntotal == 0:
return [] # Index vide
# Normaliser si métrique cosine
if self.metric == "cosine":
norm = np.linalg.norm(query_vector)
if norm > 0:
query_vector = query_vector / norm
# Convertir en float32 et reshape
query_vector = query_vector.astype(np.float32).reshape(1, -1)
# Rechercher
k = min(k, self.index.ntotal) # Ne pas demander plus que disponible
distances, indices = self.index.search(query_vector, k)
# Convertir en SearchResults
results = []
for dist, idx in zip(distances[0], indices[0]):
if idx == -1: # Pas de résultat
continue
# Récupérer métadonnées
meta = self.metadata_store.get(int(idx), {})
# Convertir distance en similarité
if self.metric == "cosine":
# Pour inner product avec vecteurs normalisés, distance = similarité
similarity = float(dist)
elif self.metric == "l2":
# Convertir distance L2 en similarité approximative
similarity = 1.0 / (1.0 + float(dist))
else:
similarity = float(dist)
# Filtrer par similarité minimale
if min_similarity is not None and similarity < min_similarity:
continue
results.append(SearchResult(
embedding_id=meta.get("embedding_id", f"unknown_{idx}"),
similarity=similarity,
distance=float(dist),
metadata=meta.get("metadata", {})
))
return results
def remove_embedding(self, faiss_id: int) -> bool:
"""
Supprimer un embedding de l'index
Note: FAISS ne supporte pas la suppression directe.
Cette méthode supprime juste les métadonnées.
Pour vraiment supprimer, il faut reconstruire l'index.
Args:
faiss_id: ID FAISS de l'embedding
Returns:
True si supprimé, False si non trouvé
"""
if faiss_id in self.metadata_store:
del self.metadata_store[faiss_id]
return True
return False
def get_metadata(self, faiss_id: int) -> Optional[Dict[str, Any]]:
"""Récupérer les métadonnées d'un embedding"""
return self.metadata_store.get(faiss_id)
def save(self, index_path: Path, metadata_path: Path) -> None:
"""
Sauvegarder l'index et les métadonnées
Args:
index_path: Chemin pour sauvegarder l'index FAISS
metadata_path: Chemin pour sauvegarder les métadonnées
"""
# Créer répertoires si nécessaire
index_path.parent.mkdir(parents=True, exist_ok=True)
metadata_path.parent.mkdir(parents=True, exist_ok=True)
# Si GPU, ramener sur CPU avant sauvegarde
index_to_save = self.index
if self.use_gpu:
try:
index_to_save = faiss.index_gpu_to_cpu(self.index)
except (RuntimeError, AttributeError):
pass # Déjà sur CPU ou pas de GPU
# Sauvegarder index FAISS
faiss.write_index(index_to_save, str(index_path))
# Sauvegarder métadonnées
metadata = {
"dimensions": self.dimensions,
"index_type": self.index_type,
"metric": self.metric,
"next_id": self.next_id,
"metadata_store": self.metadata_store,
"nlist": self.nlist,
"nprobe": self.nprobe,
"is_trained": self.is_trained,
"auto_optimize": self.auto_optimize
}
with open(metadata_path, 'wb') as f:
pickle.dump(metadata, f)
@classmethod
def load(cls, index_path: Path, metadata_path: Path, use_gpu: bool = False) -> 'FAISSManager':
"""
Charger un index et ses métadonnées
Args:
index_path: Chemin de l'index FAISS
metadata_path: Chemin des métadonnées
use_gpu: Charger sur GPU si disponible
Returns:
FAISSManager chargé
"""
# Charger métadonnées
with open(metadata_path, 'rb') as f:
metadata = pickle.load(f)
# Créer instance
manager = cls(
dimensions=metadata["dimensions"],
index_type=metadata["index_type"],
metric=metadata["metric"],
nlist=metadata.get("nlist"),
nprobe=metadata.get("nprobe", 8),
use_gpu=use_gpu,
auto_optimize=metadata.get("auto_optimize", True)
)
# Charger index FAISS
manager.index = faiss.read_index(str(index_path))
# Migrer vers GPU si demandé
if use_gpu and manager.gpu_resources is not None:
try:
manager.index = faiss.index_cpu_to_gpu(manager.gpu_resources, 0, manager.index)
except Exception as e:
logger.warning(f"Failed to move loaded index to GPU: {e}")
# Restaurer métadonnées
manager.next_id = metadata["next_id"]
manager.metadata_store = metadata["metadata_store"]
manager.is_trained = metadata.get("is_trained", True)
return manager
def get_stats(self) -> Dict[str, Any]:
"""Récupérer statistiques de l'index"""
stats = {
"dimensions": self.dimensions,
"index_type": self.index_type,
"metric": self.metric,
"total_vectors": self.index.ntotal,
"metadata_count": len(self.metadata_store),
"is_trained": self.is_trained,
"use_gpu": self.use_gpu
}
# Ajouter stats spécifiques IVF
if self.index_type == "IVF" and self.is_trained:
stats["nlist"] = self.index.nlist
stats["nprobe"] = self.index.nprobe
# Calculer nlist optimal pour comparaison
if self.index.ntotal > 0:
optimal_nlist = self._calculate_nlist(self.index.ntotal)
stats["optimal_nlist"] = optimal_nlist
stats["nlist_efficiency"] = min(1.0, self.index.nlist / optimal_nlist)
return stats
def clear(self) -> None:
"""
Vider complètement l'index + reset état d'entraînement.
Auteur : Dom, Alice Kiro - 22 décembre 2025
Amélioration pour FAISS Rebuild Propre:
- Reset complet de l'état IVF training
- Réinitialisation des training_vectors
- Gestion correcte du flag is_trained selon le type d'index
"""
self.index = self._create_index()
self.metadata_store.clear()
self.next_id = 0
# IMPORTANT: reset IVF training state
self.training_vectors.clear()
self.is_trained = (self.index_type == "Flat")
def reindex(self, items, force_train_ivf: bool = True) -> int:
"""
Reconstruit l'index à partir d'une source canonique (vecteurs).
Auteur : Dom, Alice Kiro - 22 décembre 2025
Stratégie FAISS Rebuild Propre: "1 prototype = 1 entrée"
- Clear complet avant reconstruction
- Ajout sécurisé avec validation des vecteurs
- Force training IVF même pour petits volumes
- Retour du nombre d'éléments indexés
Args:
items: Iterable[(embedding_id: str, vector: np.ndarray, metadata: dict)]
force_train_ivf: Forcer l'entraînement IVF même avec peu de vecteurs
Returns:
Nombre d'items indexés avec succès
"""
logger.info(f"FAISS reindex started with force_train_ivf={force_train_ivf}")
# Clear complet avant reconstruction
self.clear()
count = 0
for embedding_id, vector, metadata in items:
if vector is None:
logger.debug(f"Skipping None vector for {embedding_id}")
continue
try:
self.add_embedding(embedding_id, vector, metadata or {})
count += 1
except Exception as e:
logger.warning(f"Failed to add embedding {embedding_id}: {e}")
continue
# Si IVF + petit volume, add_embedding ne déclenche pas forcément l'entraînement
if (self.index_type == "IVF" and force_train_ivf and
(not self.is_trained) and self.training_vectors):
logger.info(f"Force training IVF with {len(self.training_vectors)} vectors")
self._train_ivf_index()
logger.info(f"FAISS reindex completed: {count} items indexed")
return count
def rebuild_index(self) -> None:
"""
Reconstruire l'index depuis les métadonnées
Utile après suppressions pour compacter l'index.
Note: Nécessite d'avoir les vecteurs originaux.
"""
# TODO: Implémenter si nécessaire
# Nécessiterait de stocker les vecteurs dans metadata_store
raise NotImplementedError("Rebuild not yet implemented")
# ============================================================================
# Fonctions utilitaires
# ============================================================================
def create_flat_index(dimensions: int, metric: str = "cosine") -> FAISSManager:
"""
Créer un index FAISS Flat (recherche exhaustive)
Args:
dimensions: Nombre de dimensions
metric: Métrique ("cosine", "l2", "ip")
Returns:
FAISSManager configuré
"""
return FAISSManager(dimension=dimensions, index_type="Flat", metric=metric)
def create_ivf_index(dimensions: int, metric: str = "cosine") -> FAISSManager:
"""
Créer un index FAISS IVF (recherche approximative rapide)
Args:
dimensions: Nombre de dimensions
metric: Métrique ("cosine", "l2")
Returns:
FAISSManager configuré
"""
return FAISSManager(dimension=dimensions, index_type="IVF", metric=metric)

View File

@@ -1,613 +0,0 @@
"""
FusionEngine - Fusion Multi-Modale d'Embeddings
Fusionne plusieurs embeddings (image, texte, titre, UI) en un seul vecteur
avec pondération configurable et normalisation L2.
Tâche 5.2: Lazy loading des embeddings avec WeakValueDictionary.
"""
from typing import Dict, List, Optional
import numpy as np
from dataclasses import dataclass
import weakref
import logging
from pathlib import Path
from ..models.state_embedding import (
StateEmbedding,
EmbeddingComponent,
DEFAULT_FUSION_WEIGHTS
)
logger = logging.getLogger(__name__)
@dataclass
class FusionConfig:
"""Configuration de la fusion"""
method: str = "weighted" # weighted ou concat_projection
normalize: bool = True # Normaliser le vecteur final
weights: Dict[str, float] = None # Poids personnalisés
def __post_init__(self):
if self.weights is None:
self.weights = DEFAULT_FUSION_WEIGHTS.copy()
# Valider que les poids somment à 1.0 pour weighted
if self.method == "weighted":
total = sum(self.weights.values())
if not (0.99 <= total <= 1.01):
raise ValueError(
f"Weights must sum to 1.0 for weighted fusion, got {total}"
)
class FusionEngine:
"""
Moteur de fusion multi-modale avec lazy loading optimisé
Fusionne des embeddings de différentes modalités (image, texte, UI)
en un seul vecteur représentant l'état complet de l'écran.
Tâche 5.2: Implémente lazy loading avec WeakValueDictionary pour
éviter les rechargements multiples tout en permettant le garbage collection.
"""
def __init__(self, config: Optional[FusionConfig] = None):
"""
Initialiser le moteur de fusion avec lazy loading
Args:
config: Configuration de fusion (utilise config par défaut si None)
"""
self.config = config or FusionConfig()
# Tâche 5.2: Cache lazy loading avec WeakValueDictionary
# Permet le garbage collection automatique des embeddings non utilisés
self._embedding_cache: weakref.WeakValueDictionary = weakref.WeakValueDictionary()
self._cache_stats = {
'hits': 0,
'misses': 0,
'loads': 0,
'evictions': 0
}
def fuse(self,
embeddings: Dict[str, np.ndarray],
weights: Optional[Dict[str, float]] = None) -> np.ndarray:
"""
Fusionner plusieurs embeddings en un seul vecteur
Args:
embeddings: Dict {modalité: vecteur}
e.g., {"image": vec1, "text": vec2, "title": vec3, "ui": vec4}
weights: Poids personnalisés (optionnel, utilise config par défaut)
Returns:
Vecteur fusionné (normalisé si config.normalize=True)
Raises:
ValueError: Si les dimensions ne correspondent pas ou poids invalides
"""
if not embeddings:
raise ValueError("No embeddings provided for fusion")
# Utiliser poids de config ou poids fournis
fusion_weights = weights or self.config.weights
# Vérifier que toutes les modalités ont le même nombre de dimensions
dimensions = None
for modality, vector in embeddings.items():
if dimensions is None:
dimensions = vector.shape[0]
elif vector.shape[0] != dimensions:
raise ValueError(
f"All embeddings must have same dimensions. "
f"Expected {dimensions}, got {vector.shape[0]} for {modality}"
)
if self.config.method == "weighted":
fused = self._fuse_weighted(embeddings, fusion_weights)
elif self.config.method == "concat_projection":
fused = self._fuse_concat_projection(embeddings, fusion_weights)
else:
raise ValueError(f"Unknown fusion method: {self.config.method}")
# Normaliser si demandé
if self.config.normalize:
fused = self._normalize_l2(fused)
return fused
def _fuse_weighted(self,
embeddings: Dict[str, np.ndarray],
weights: Dict[str, float]) -> np.ndarray:
"""
Fusion pondérée simple : somme pondérée des vecteurs
fused = w1*v1 + w2*v2 + w3*v3 + w4*v4
"""
# Initialiser vecteur résultat
first_vector = next(iter(embeddings.values()))
fused = np.zeros_like(first_vector, dtype=np.float32)
# Somme pondérée
for modality, vector in embeddings.items():
weight = weights.get(modality, 0.0)
fused += weight * vector
return fused
def _fuse_concat_projection(self,
embeddings: Dict[str, np.ndarray],
weights: Dict[str, float]) -> np.ndarray:
"""
Fusion par concaténation + projection
Concatène tous les vecteurs puis projette vers dimension cible.
Note: Pour l'instant, on fait une simple moyenne pondérée.
TODO: Implémenter vraie projection avec matrice apprise.
"""
# Pour l'instant, utiliser fusion pondérée
# Dans une version future, on pourrait apprendre une matrice de projection
return self._fuse_weighted(embeddings, weights)
def _normalize_l2(self, vector: np.ndarray) -> np.ndarray:
"""
Normaliser un vecteur avec norme L2
normalized = vector / ||vector||_2
"""
norm = np.linalg.norm(vector)
if norm < 1e-10: # Éviter division par zéro
return vector
return vector / norm
def create_state_embedding(self,
embedding_id: str,
embeddings: Dict[str, np.ndarray],
vector_save_path: str,
weights: Optional[Dict[str, float]] = None,
metadata: Optional[Dict] = None) -> StateEmbedding:
"""
Créer un StateEmbedding complet depuis des embeddings individuels
Args:
embedding_id: ID unique pour cet embedding
embeddings: Dict {modalité: vecteur}
vector_save_path: Chemin où sauvegarder le vecteur fusionné
weights: Poids personnalisés (optionnel)
metadata: Métadonnées additionnelles
Returns:
StateEmbedding avec vecteur fusionné sauvegardé
"""
# Fusionner les embeddings
fused_vector = self.fuse(embeddings, weights)
# Créer les composants
fusion_weights = weights or self.config.weights
components = {}
for modality, vector in embeddings.items():
# Pour l'instant, on ne sauvegarde pas les vecteurs individuels
# On pourrait les sauvegarder si nécessaire
components[modality] = EmbeddingComponent(
weight=fusion_weights.get(modality, 0.0),
vector_id=f"{vector_save_path}_{modality}.npy",
source_text=None
)
# Créer StateEmbedding
dimensions = fused_vector.shape[0]
state_emb = StateEmbedding(
embedding_id=embedding_id,
vector_id=vector_save_path,
dimensions=dimensions,
fusion_method=self.config.method,
components=components,
metadata=metadata or {}
)
# Sauvegarder le vecteur fusionné
state_emb.save_vector(fused_vector)
return state_emb
def compute_similarity(self,
emb1: StateEmbedding,
emb2: StateEmbedding) -> float:
"""
Calculer similarité cosinus entre deux StateEmbeddings
Args:
emb1: Premier embedding
emb2: Deuxième embedding
Returns:
Similarité cosinus dans [-1, 1]
"""
return emb1.compute_similarity(emb2)
def batch_fuse(self,
batch_embeddings: List[Dict[str, np.ndarray]],
weights: Optional[Dict[str, float]] = None) -> List[np.ndarray]:
"""
Fusionner un batch d'embeddings en parallèle
Args:
batch_embeddings: Liste de dicts {modalité: vecteur}
weights: Poids personnalisés (optionnel)
Returns:
Liste de vecteurs fusionnés
"""
return [self.fuse(embs, weights) for embs in batch_embeddings]
def get_config(self) -> FusionConfig:
"""Récupérer la configuration actuelle"""
return self.config
def set_weights(self, weights: Dict[str, float]) -> None:
"""
Mettre à jour les poids de fusion
Args:
weights: Nouveaux poids
Raises:
ValueError: Si les poids ne somment pas à 1.0 (pour weighted)
"""
if self.config.method == "weighted":
total = sum(weights.values())
if not (0.99 <= total <= 1.01):
raise ValueError(
f"Weights must sum to 1.0 for weighted fusion, got {total}"
)
self.config.weights = weights.copy()
# ============================================================================
# Fonctions utilitaires
# ============================================================================
def create_default_fusion_engine() -> FusionEngine:
"""Créer un FusionEngine avec configuration par défaut"""
return FusionEngine(FusionConfig())
def normalize_vector(vector: np.ndarray) -> np.ndarray:
"""
Normaliser un vecteur avec norme L2
Args:
vector: Vecteur à normaliser
Returns:
Vecteur normalisé
"""
norm = np.linalg.norm(vector)
if norm < 1e-10:
return vector
return vector / norm
def validate_weights(weights: Dict[str, float],
method: str = "weighted") -> bool:
"""
Valider que les poids sont corrects
Args:
weights: Poids à valider
method: Méthode de fusion
Returns:
True si valides, False sinon
"""
if method == "weighted":
total = sum(weights.values())
return 0.99 <= total <= 1.01
return True
def fuse_batch(
self,
embeddings_batch: List[Dict[str, np.ndarray]],
weights: Optional[Dict[str, float]] = None
) -> np.ndarray:
"""
Fusionner un batch d'embeddings en parallèle pour efficacité.
Args:
embeddings_batch: Liste de dicts {modalité: vecteur}
weights: Poids personnalisés (optionnel)
Returns:
Array numpy de shape (batch_size, embedding_dim) avec vecteurs fusionnés
Note:
Cette méthode est optimisée pour traiter plusieurs embeddings
en une seule opération vectorisée, ce qui est plus rapide que
de fusionner un par un.
"""
if not embeddings_batch:
raise ValueError("Empty batch provided")
batch_size = len(embeddings_batch)
fusion_weights = weights or self.config.weights
# Déterminer les dimensions depuis le premier élément
first_emb = embeddings_batch[0]
first_vector = next(iter(first_emb.values()))
embedding_dim = first_vector.shape[0]
# Préparer le résultat
fused_batch = np.zeros((batch_size, embedding_dim), dtype=np.float32)
# Traiter chaque modalité pour tout le batch
for modality in first_emb.keys():
weight = fusion_weights.get(modality, 0.0)
if weight == 0.0:
continue
# Collecter tous les vecteurs de cette modalité
modality_vectors = []
for emb_dict in embeddings_batch:
if modality in emb_dict:
modality_vectors.append(emb_dict[modality])
else:
# Si modalité manquante, utiliser vecteur zéro
modality_vectors.append(np.zeros(embedding_dim, dtype=np.float32))
# Convertir en array numpy (batch_size, embedding_dim)
modality_batch = np.array(modality_vectors, dtype=np.float32)
# Ajouter contribution pondérée
fused_batch += weight * modality_batch
# Normaliser si demandé
if self.config.normalize:
# Normalisation L2 pour chaque vecteur du batch
norms = np.linalg.norm(fused_batch, axis=1, keepdims=True)
# Éviter division par zéro
norms = np.where(norms < 1e-10, 1.0, norms)
fused_batch = fused_batch / norms
return fused_batch
def create_state_embeddings_batch(
self,
embedding_ids: List[str],
embeddings_batch: List[Dict[str, np.ndarray]],
vector_save_paths: List[str],
weights: Optional[Dict[str, float]] = None,
metadata_batch: Optional[List[Dict]] = None
) -> List[StateEmbedding]:
"""
Créer un batch de StateEmbeddings de manière optimisée.
Args:
embedding_ids: Liste des IDs uniques
embeddings_batch: Liste de dicts {modalité: vecteur}
vector_save_paths: Liste des chemins de sauvegarde
weights: Poids personnalisés (optionnel)
metadata_batch: Liste de métadonnées (optionnel)
Returns:
Liste de StateEmbeddings créés
Note:
Cette méthode est ~3-5x plus rapide que de créer les embeddings
un par un grâce au traitement vectorisé.
"""
if not (len(embedding_ids) == len(embeddings_batch) == len(vector_save_paths)):
raise ValueError("All input lists must have the same length")
batch_size = len(embedding_ids)
# Fusionner tout le batch en une seule opération
fused_vectors = self.fuse_batch(embeddings_batch, weights)
# Créer les StateEmbeddings
state_embeddings = []
fusion_weights = weights or self.config.weights
for i in range(batch_size):
embedding_id = embedding_ids[i]
embeddings = embeddings_batch[i]
vector_save_path = vector_save_paths[i]
metadata = metadata_batch[i] if metadata_batch else None
fused_vector = fused_vectors[i]
# Créer les composants
components = {}
for modality, vector in embeddings.items():
components[modality] = EmbeddingComponent(
weight=fusion_weights.get(modality, 0.0),
vector_id=f"{vector_save_path}_{modality}.npy",
source_text=None
)
# Créer StateEmbedding
dimensions = fused_vector.shape[0]
state_emb = StateEmbedding(
embedding_id=embedding_id,
vector_id=vector_save_path,
dimensions=dimensions,
fusion_method=self.config.method,
components=components,
metadata=metadata or {}
)
# Sauvegarder le vecteur fusionné
state_emb.save_vector(fused_vector)
state_embeddings.append(state_emb)
return state_embeddings
def compute_similarity_batch(
self,
query_embedding: StateEmbedding,
candidate_embeddings: List[StateEmbedding]
) -> np.ndarray:
"""
Calculer la similarité entre un embedding query et un batch de candidats.
Args:
query_embedding: Embedding de requête
candidate_embeddings: Liste d'embeddings candidats
Returns:
Array numpy de similarités (batch_size,)
Note:
Utilise des opérations vectorisées pour calculer toutes les
similarités en une seule opération matricielle.
"""
# Charger le vecteur query
query_vector = query_embedding.get_vector()
# Charger tous les vecteurs candidats
candidate_vectors = []
for emb in candidate_embeddings:
candidate_vectors.append(emb.get_vector())
# Convertir en matrice (batch_size, embedding_dim)
candidates_matrix = np.array(candidate_vectors, dtype=np.float32)
# Calcul vectorisé : similarité cosinus = dot product (si normalisés)
# similarities = candidates_matrix @ query_vector
similarities = np.dot(candidates_matrix, query_vector)
return similarities
def load_embedding_lazy(self, embedding_path: str, force_reload: bool = False) -> Optional[np.ndarray]:
"""
Charger un embedding avec lazy loading et cache.
Tâche 5.2: Lazy loading des embeddings avec cache WeakValueDictionary.
Chargement à la demande depuis le disque avec éviction automatique.
Args:
embedding_path: Chemin vers le fichier embedding (.npy)
force_reload: Forcer le rechargement depuis le disque
Returns:
Array numpy de l'embedding ou None si erreur
"""
if not embedding_path:
return None
# Vérifier le cache d'abord (sauf si force_reload)
if not force_reload and embedding_path in self._embedding_cache:
self._cache_stats['hits'] += 1
logger.debug(f"Embedding cache hit: {Path(embedding_path).name}")
return self._embedding_cache[embedding_path]
# Cache miss - charger depuis le disque
self._cache_stats['misses'] += 1
try:
if not Path(embedding_path).exists():
logger.warning(f"Embedding file not found: {embedding_path}")
return None
logger.debug(f"Loading embedding from disk: {Path(embedding_path).name}")
embedding = np.load(embedding_path)
# Valider le format
if not isinstance(embedding, np.ndarray) or embedding.ndim != 1:
logger.error(f"Invalid embedding format in {embedding_path}")
return None
# Ajouter au cache (WeakValueDictionary gère l'éviction automatique)
self._embedding_cache[embedding_path] = embedding
self._cache_stats['loads'] += 1
logger.debug(f"Embedding loaded: {embedding.shape} from {Path(embedding_path).name}")
return embedding
except Exception as e:
logger.error(f"Error loading embedding from {embedding_path}: {e}")
return None
def fuse_with_lazy_loading(self,
embedding_paths: Dict[str, str],
weights: Optional[Dict[str, float]] = None) -> Optional[np.ndarray]:
"""
Fusionner des embeddings avec lazy loading depuis les chemins de fichiers.
Tâche 5.2: Version optimisée qui charge les embeddings à la demande.
Args:
embedding_paths: Dict {modalité: chemin_fichier}
weights: Poids personnalisés (optionnel)
Returns:
Vecteur fusionné ou None si erreur
"""
if not embedding_paths:
logger.warning("No embedding paths provided for lazy fusion")
return None
# Charger les embeddings avec lazy loading
embeddings = {}
for modality, path in embedding_paths.items():
embedding = self.load_embedding_lazy(path)
if embedding is not None:
embeddings[modality] = embedding
else:
logger.warning(f"Failed to load embedding for modality '{modality}' from {path}")
if not embeddings:
logger.error("No embeddings could be loaded for fusion")
return None
# Fusionner normalement
return self.fuse(embeddings, weights)
def get_cache_stats(self) -> Dict[str, int]:
"""
Obtenir les statistiques du cache d'embeddings.
Returns:
Dict avec hits, misses, loads, cache_size
"""
return {
**self._cache_stats,
'cache_size': len(self._embedding_cache)
}
def clear_embedding_cache(self) -> None:
"""
Vider le cache d'embeddings.
Utile pour libérer la mémoire ou forcer le rechargement.
"""
cache_size = len(self._embedding_cache)
self._embedding_cache.clear()
self._cache_stats['evictions'] += cache_size
logger.info(f"Cleared embedding cache ({cache_size} entries)")
def preload_embeddings(self, embedding_paths: List[str]) -> int:
"""
Précharger des embeddings dans le cache.
Utile pour optimiser les performances en chargeant
les embeddings fréquemment utilisés à l'avance.
Args:
embedding_paths: Liste des chemins à précharger
Returns:
Nombre d'embeddings préchargés avec succès
"""
loaded_count = 0
for path in embedding_paths:
if self.load_embedding_lazy(path) is not None:
loaded_count += 1
logger.info(f"Preloaded {loaded_count}/{len(embedding_paths)} embeddings")
return loaded_count

View File

@@ -1,388 +0,0 @@
"""
Similarity - Calculs de Similarité et Distance
Fonctions pour calculer différentes métriques de similarité et distance
entre vecteurs d'embeddings.
"""
import numpy as np
from typing import Union, List
def cosine_similarity(vec1: np.ndarray, vec2: np.ndarray) -> float:
"""
Calculer similarité cosinus entre deux vecteurs
similarity = (vec1 · vec2) / (||vec1|| * ||vec2||)
Args:
vec1: Premier vecteur
vec2: Deuxième vecteur
Returns:
Similarité cosinus dans [-1, 1]
1 = identiques, 0 = orthogonaux, -1 = opposés
Raises:
ValueError: Si dimensions ne correspondent pas
"""
if vec1.shape != vec2.shape:
raise ValueError(
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
)
# Produit scalaire
dot_product = np.dot(vec1, vec2)
# Normes
norm1 = np.linalg.norm(vec1)
norm2 = np.linalg.norm(vec2)
# Éviter division par zéro
if norm1 == 0 or norm2 == 0:
return 0.0
# Similarité cosinus
similarity = dot_product / (norm1 * norm2)
# Clamp dans [-1, 1] pour éviter erreurs numériques
similarity = np.clip(similarity, -1.0, 1.0)
return float(similarity)
def euclidean_distance(vec1: np.ndarray, vec2: np.ndarray) -> float:
"""
Calculer distance euclidienne (L2) entre deux vecteurs
distance = ||vec1 - vec2||_2 = sqrt(sum((vec1 - vec2)^2))
Args:
vec1: Premier vecteur
vec2: Deuxième vecteur
Returns:
Distance euclidienne (>= 0)
Raises:
ValueError: Si dimensions ne correspondent pas
"""
if vec1.shape != vec2.shape:
raise ValueError(
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
)
return float(np.linalg.norm(vec1 - vec2))
def manhattan_distance(vec1: np.ndarray, vec2: np.ndarray) -> float:
"""
Calculer distance de Manhattan (L1) entre deux vecteurs
distance = sum(|vec1 - vec2|)
Args:
vec1: Premier vecteur
vec2: Deuxième vecteur
Returns:
Distance de Manhattan (>= 0)
Raises:
ValueError: Si dimensions ne correspondent pas
"""
if vec1.shape != vec2.shape:
raise ValueError(
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
)
return float(np.sum(np.abs(vec1 - vec2)))
def dot_product(vec1: np.ndarray, vec2: np.ndarray) -> float:
"""
Calculer produit scalaire entre deux vecteurs
dot = vec1 · vec2 = sum(vec1 * vec2)
Args:
vec1: Premier vecteur
vec2: Deuxième vecteur
Returns:
Produit scalaire
Raises:
ValueError: Si dimensions ne correspondent pas
"""
if vec1.shape != vec2.shape:
raise ValueError(
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
)
return float(np.dot(vec1, vec2))
def normalize_l2(vector: np.ndarray, epsilon: float = 1e-10) -> np.ndarray:
"""
Normaliser un vecteur avec norme L2
normalized = vector / ||vector||_2
Args:
vector: Vecteur à normaliser
epsilon: Valeur minimale pour éviter division par zéro
Returns:
Vecteur normalisé (norme L2 = 1.0)
"""
norm = np.linalg.norm(vector)
if norm < epsilon:
return vector
return vector / norm
def normalize_l1(vector: np.ndarray, epsilon: float = 1e-10) -> np.ndarray:
"""
Normaliser un vecteur avec norme L1
normalized = vector / sum(|vector|)
Args:
vector: Vecteur à normaliser
epsilon: Valeur minimale pour éviter division par zéro
Returns:
Vecteur normalisé (norme L1 = 1.0)
"""
norm = np.sum(np.abs(vector))
if norm < epsilon:
return vector
return vector / norm
def batch_cosine_similarity(vectors: List[np.ndarray],
query: np.ndarray) -> np.ndarray:
"""
Calculer similarité cosinus entre une requête et un batch de vecteurs
Args:
vectors: Liste de vecteurs
query: Vecteur de requête
Returns:
Array de similarités
"""
# Convertir en matrice
matrix = np.array(vectors)
# Normaliser
matrix_norm = matrix / (np.linalg.norm(matrix, axis=1, keepdims=True) + 1e-10)
query_norm = query / (np.linalg.norm(query) + 1e-10)
# Produit matriciel
similarities = np.dot(matrix_norm, query_norm)
# Clamp
similarities = np.clip(similarities, -1.0, 1.0)
return similarities
def pairwise_cosine_similarity(vectors: List[np.ndarray]) -> np.ndarray:
"""
Calculer matrice de similarité cosinus entre tous les vecteurs
Args:
vectors: Liste de vecteurs
Returns:
Matrice de similarité (n x n)
"""
# Convertir en matrice
matrix = np.array(vectors)
# Normaliser
matrix_norm = matrix / (np.linalg.norm(matrix, axis=1, keepdims=True) + 1e-10)
# Produit matriciel
similarity_matrix = np.dot(matrix_norm, matrix_norm.T)
# Clamp
similarity_matrix = np.clip(similarity_matrix, -1.0, 1.0)
return similarity_matrix
def angular_distance(vec1: np.ndarray, vec2: np.ndarray) -> float:
"""
Calculer distance angulaire entre deux vecteurs
distance = arccos(cosine_similarity) / π
Args:
vec1: Premier vecteur
vec2: Deuxième vecteur
Returns:
Distance angulaire dans [0, 1]
"""
similarity = cosine_similarity(vec1, vec2)
angle = np.arccos(np.clip(similarity, -1.0, 1.0))
return float(angle / np.pi)
def jaccard_similarity(vec1: np.ndarray, vec2: np.ndarray) -> float:
"""
Calculer similarité de Jaccard pour vecteurs binaires
similarity = |intersection| / |union|
Args:
vec1: Premier vecteur binaire
vec2: Deuxième vecteur binaire
Returns:
Similarité de Jaccard dans [0, 1]
"""
if vec1.shape != vec2.shape:
raise ValueError(
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
)
intersection = np.sum(np.logical_and(vec1, vec2))
union = np.sum(np.logical_or(vec1, vec2))
if union == 0:
return 0.0
return float(intersection / union)
def hamming_distance(vec1: np.ndarray, vec2: np.ndarray) -> float:
"""
Calculer distance de Hamming pour vecteurs binaires
distance = nombre de positions différentes
Args:
vec1: Premier vecteur binaire
vec2: Deuxième vecteur binaire
Returns:
Distance de Hamming
"""
if vec1.shape != vec2.shape:
raise ValueError(
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
)
return float(np.sum(vec1 != vec2))
# ============================================================================
# Fonctions de conversion
# ============================================================================
def similarity_to_distance(similarity: float,
method: str = "cosine") -> float:
"""
Convertir similarité en distance
Args:
similarity: Valeur de similarité
method: Méthode ("cosine", "angular")
Returns:
Distance correspondante
"""
if method == "cosine":
# distance = 1 - similarity (pour cosine dans [0, 1])
return 1.0 - similarity
elif method == "angular":
# distance angulaire
angle = np.arccos(np.clip(similarity, -1.0, 1.0))
return float(angle / np.pi)
else:
raise ValueError(f"Unknown method: {method}")
def distance_to_similarity(distance: float,
method: str = "euclidean") -> float:
"""
Convertir distance en similarité
Args:
distance: Valeur de distance
method: Méthode ("euclidean", "manhattan")
Returns:
Similarité correspondante dans [0, 1]
"""
if method in ["euclidean", "manhattan"]:
# similarity = 1 / (1 + distance)
return 1.0 / (1.0 + distance)
else:
raise ValueError(f"Unknown method: {method}")
# ============================================================================
# Fonctions utilitaires
# ============================================================================
def is_normalized(vector: np.ndarray,
norm_type: str = "l2",
tolerance: float = 1e-6) -> bool:
"""
Vérifier si un vecteur est normalisé
Args:
vector: Vecteur à vérifier
norm_type: Type de norme ("l2" ou "l1")
tolerance: Tolérance pour la vérification
Returns:
True si normalisé, False sinon
"""
if norm_type == "l2":
norm = np.linalg.norm(vector)
elif norm_type == "l1":
norm = np.sum(np.abs(vector))
else:
raise ValueError(f"Unknown norm type: {norm_type}")
return abs(norm - 1.0) < tolerance
def compute_centroid(vectors: List[np.ndarray]) -> np.ndarray:
"""
Calculer le centroïde (moyenne) d'un ensemble de vecteurs
Args:
vectors: Liste de vecteurs
Returns:
Vecteur centroïde
"""
if not vectors:
raise ValueError("Cannot compute centroid of empty list")
matrix = np.array(vectors)
return np.mean(matrix, axis=0)
def compute_variance(vectors: List[np.ndarray]) -> float:
"""
Calculer la variance d'un ensemble de vecteurs
Args:
vectors: Liste de vecteurs
Returns:
Variance totale
"""
if not vectors:
raise ValueError("Cannot compute variance of empty list")
matrix = np.array(vectors)
return float(np.var(matrix))

View File

@@ -1,395 +0,0 @@
"""
StateEmbeddingBuilder - Construction de State Embeddings Complets
Construit des State Embeddings en fusionnant les embeddings de toutes les modalités
(image, texte, titre, UI) depuis un ScreenState.
Utilise OpenCLIP pour générer de vrais embeddings au lieu de vecteurs aléatoires.
"""
from typing import Dict, Optional, Any
from pathlib import Path
import logging
import numpy as np
from datetime import datetime
from PIL import Image
logger = logging.getLogger(__name__)
from ..models.screen_state import ScreenState
from ..models.state_embedding import StateEmbedding, EmbeddingComponent
from .fusion_engine import FusionEngine, FusionConfig
from .clip_embedder import CLIPEmbedder
class StateEmbeddingBuilder:
"""
Constructeur de State Embeddings
Prend un ScreenState et génère un State Embedding complet en :
1. Calculant les embeddings pour chaque modalité (image, texte, titre, UI)
2. Fusionnant ces embeddings avec le FusionEngine
3. Sauvegardant le résultat
"""
def __init__(self,
fusion_engine: Optional[FusionEngine] = None,
embedders: Optional[Dict[str, Any]] = None,
output_dir: Optional[Path] = None,
use_clip: bool = True):
"""
Initialiser le builder
Args:
fusion_engine: Moteur de fusion (crée un par défaut si None)
embedders: Dict d'embedders pour chaque modalité
{"image": ImageEmbedder, "text": TextEmbedder, ...}
output_dir: Répertoire de sortie pour les vecteurs
use_clip: Si True, utilise OpenCLIP pour les embeddings (recommandé)
"""
self.fusion_engine = fusion_engine or FusionEngine()
self.output_dir = output_dir or Path("data/embeddings")
self.output_dir.mkdir(parents=True, exist_ok=True)
# Initialiser OpenCLIP si demandé
self.clip_embedder = None
if use_clip:
try:
logger.info("Initialisation OpenCLIP pour embeddings...")
self.clip_embedder = CLIPEmbedder()
logger.info("✓ OpenCLIP initialisé")
except Exception as e:
logger.warning(f"Impossible d'initialiser OpenCLIP: {e}")
logger.info("Utilisation des embedders fournis ou vecteurs par défaut")
# Utiliser embedders fournis ou créer avec CLIP
if embedders:
self.embedders = embedders
elif self.clip_embedder:
# Utiliser CLIP pour toutes les modalités
self.embedders = {
"image": self.clip_embedder,
"text": self.clip_embedder,
"title": self.clip_embedder,
"ui": self.clip_embedder
}
else:
self.embedders = {}
def build(self,
screen_state: ScreenState,
embedding_id: Optional[str] = None,
compute_embeddings: bool = True) -> StateEmbedding:
"""
Construire un State Embedding depuis un ScreenState
Args:
screen_state: État d'écran à embedder
embedding_id: ID unique (généré si None)
compute_embeddings: Si False, utilise des embeddings pré-calculés
Returns:
StateEmbedding complet avec vecteur fusionné
"""
# Générer ID si nécessaire
if embedding_id is None:
embedding_id = self._generate_embedding_id(screen_state)
# Calculer ou récupérer embeddings pour chaque modalité
if compute_embeddings:
embeddings = self._compute_all_embeddings(screen_state)
else:
embeddings = self._load_precomputed_embeddings(screen_state)
# Chemin de sauvegarde du vecteur fusionné
vector_path = self.output_dir / f"{embedding_id}.npy"
# Créer State Embedding avec fusion
state_embedding = self.fusion_engine.create_state_embedding(
embedding_id=embedding_id,
embeddings=embeddings,
vector_save_path=str(vector_path),
metadata={
"screen_state_id": screen_state.screen_state_id,
"timestamp": screen_state.timestamp.isoformat(),
"window_title": getattr(screen_state.window, 'title', ''),
"created_at": datetime.now().isoformat()
}
)
# Sauvegarder métadonnées
metadata_path = self.output_dir / f"{embedding_id}_metadata.json"
state_embedding.save_to_file(metadata_path)
return state_embedding
def _compute_all_embeddings(self,
screen_state: ScreenState) -> Dict[str, np.ndarray]:
"""
Calculer embeddings pour toutes les modalités
Args:
screen_state: État d'écran
Returns:
Dict {modalité: vecteur}
"""
embeddings = {}
# Image embedding (screenshot complet)
if "image" in self.embedders and hasattr(screen_state, 'raw'):
image_emb = self._compute_image_embedding(screen_state)
if image_emb is not None:
embeddings["image"] = image_emb
# Text embedding (texte détecté)
if "text" in self.embedders and hasattr(screen_state, 'perception'):
text_emb = self._compute_text_embedding(screen_state)
if text_emb is not None:
embeddings["text"] = text_emb
# Title embedding (titre de fenêtre)
if "title" in self.embedders and hasattr(screen_state, 'window'):
title_emb = self._compute_title_embedding(screen_state)
if title_emb is not None:
embeddings["title"] = title_emb
# UI embedding (éléments UI)
if "ui" in self.embedders and hasattr(screen_state, 'ui_elements'):
ui_emb = self._compute_ui_embedding(screen_state)
if ui_emb is not None:
embeddings["ui"] = ui_emb
# Si aucun embedding calculé, créer des vecteurs par défaut
if not embeddings:
# Utiliser dimensions par défaut (512)
default_dim = 512
embeddings = {
"image": np.random.randn(default_dim).astype(np.float32),
"text": np.random.randn(default_dim).astype(np.float32),
"title": np.random.randn(default_dim).astype(np.float32),
"ui": np.random.randn(default_dim).astype(np.float32)
}
return embeddings
def _compute_image_embedding(self, screen_state: ScreenState) -> Optional[np.ndarray]:
"""Calculer embedding de l'image (screenshot) avec OpenCLIP"""
if "image" not in self.embedders:
return None
try:
embedder = self.embedders["image"]
screenshot_path = screen_state.raw.screenshot_path
# Charger l'image
image = Image.open(screenshot_path)
# Utiliser OpenCLIP si disponible
if isinstance(embedder, CLIPEmbedder):
return embedder.embed_image(image)
# Sinon, essayer les méthodes standard
if hasattr(embedder, 'embed_image'):
return embedder.embed_image(screenshot_path)
elif hasattr(embedder, 'encode_image'):
return embedder.encode_image(screenshot_path)
elif callable(embedder):
return embedder(screenshot_path)
except Exception as e:
logger.warning(f"Failed to compute image embedding: {e}")
logger.debug("Traceback:", exc_info=True)
return None
def _compute_text_embedding(self, screen_state: ScreenState) -> Optional[np.ndarray]:
"""Calculer embedding du texte détecté avec OpenCLIP"""
if "text" not in self.embedders:
return None
try:
embedder = self.embedders["text"]
# Concaténer tous les textes détectés
texts = []
if hasattr(screen_state.perception, 'detected_texts'):
texts = screen_state.perception.detected_texts
combined_text = " ".join(texts) if texts else ""
if not combined_text:
return None
# Utiliser OpenCLIP si disponible
if isinstance(embedder, CLIPEmbedder):
return embedder.embed_text(combined_text)
# Sinon, essayer les méthodes standard
if hasattr(embedder, 'embed_text'):
return embedder.embed_text(combined_text)
elif hasattr(embedder, 'encode_text'):
return embedder.encode_text(combined_text)
elif callable(embedder):
return embedder(combined_text)
except Exception as e:
logger.warning(f"Failed to compute text embedding: {e}")
return None
def _compute_title_embedding(self, screen_state: ScreenState) -> Optional[np.ndarray]:
"""Calculer embedding du titre de fenêtre avec OpenCLIP"""
if "title" not in self.embedders:
return None
try:
embedder = self.embedders["title"]
title = getattr(screen_state.window, 'title', '')
if not title:
return None
# Utiliser OpenCLIP si disponible
if isinstance(embedder, CLIPEmbedder):
return embedder.embed_text(title)
# Sinon, essayer les méthodes standard
if hasattr(embedder, 'embed_text'):
return embedder.embed_text(title)
elif hasattr(embedder, 'encode_text'):
return embedder.encode_text(title)
elif callable(embedder):
return embedder(title)
except Exception as e:
logger.warning(f"Failed to compute title embedding: {e}")
return None
def _compute_ui_embedding(self, screen_state: ScreenState) -> Optional[np.ndarray]:
"""Calculer embedding moyen des éléments UI"""
if "ui" not in self.embedders:
return None
try:
embedder = self.embedders["ui"]
ui_elements = screen_state.ui_elements
if not ui_elements:
return None
# Calculer embedding pour chaque élément UI
ui_embeddings = []
for element in ui_elements:
# Utiliser embedding image de l'élément si disponible
if hasattr(element, 'embeddings') and element.embeddings:
if hasattr(element.embeddings, 'image_embedding_id'):
# Charger embedding pré-calculé
emb_path = Path(element.embeddings.image_embedding_id)
if emb_path.exists():
ui_embeddings.append(np.load(emb_path))
# Si pas d'embeddings pré-calculés, calculer depuis labels
if not ui_embeddings:
for element in ui_elements:
label = getattr(element, 'label', '')
if label and hasattr(embedder, 'embed_text'):
ui_embeddings.append(embedder.embed_text(label))
# Moyenne des embeddings UI
if ui_embeddings:
return np.mean(ui_embeddings, axis=0)
except Exception as e:
logger.warning(f"Failed to compute UI embedding: {e}")
return None
def _load_precomputed_embeddings(self,
screen_state: ScreenState) -> Dict[str, np.ndarray]:
"""Charger embeddings pré-calculés"""
# TODO: Implémenter chargement depuis cache
# Pour l'instant, calculer à la volée
return self._compute_all_embeddings(screen_state)
def _generate_embedding_id(self, screen_state: ScreenState) -> str:
"""Générer un ID unique pour l'embedding"""
timestamp = screen_state.timestamp.strftime("%Y%m%d_%H%M%S_%f")
return f"state_emb_{screen_state.screen_state_id}_{timestamp}"
def batch_build(self,
screen_states: list[ScreenState],
compute_embeddings: bool = True) -> list[StateEmbedding]:
"""
Construire plusieurs State Embeddings en batch
Args:
screen_states: Liste de ScreenStates
compute_embeddings: Si False, utilise embeddings pré-calculés
Returns:
Liste de StateEmbeddings
"""
return [
self.build(state, compute_embeddings=compute_embeddings)
for state in screen_states
]
def set_embedder(self, modality: str, embedder: Any) -> None:
"""
Définir un embedder pour une modalité
Args:
modality: Nom de la modalité ("image", "text", "title", "ui")
embedder: Embedder à utiliser
"""
self.embedders[modality] = embedder
def get_embedder(self, modality: str) -> Optional[Any]:
"""Récupérer l'embedder d'une modalité"""
return self.embedders.get(modality)
def set_output_dir(self, output_dir: Path) -> None:
"""Définir le répertoire de sortie"""
self.output_dir = output_dir
self.output_dir.mkdir(parents=True, exist_ok=True)
# ============================================================================
# Fonctions utilitaires
# ============================================================================
def create_builder(embedders: Optional[Dict[str, Any]] = None,
output_dir: Optional[Path] = None,
use_clip: bool = True) -> StateEmbeddingBuilder:
"""
Créer un StateEmbeddingBuilder avec configuration par défaut
Args:
embedders: Dict d'embedders optionnel
output_dir: Répertoire de sortie optionnel
use_clip: Si True, utilise OpenCLIP (recommandé)
Returns:
StateEmbeddingBuilder configuré avec OpenCLIP
"""
return StateEmbeddingBuilder(
embedders=embedders,
output_dir=output_dir,
use_clip=use_clip
)
def build_from_screen_state(screen_state: ScreenState,
embedders: Dict[str, Any],
output_dir: Path) -> StateEmbedding:
"""
Fonction helper pour construire rapidement un State Embedding
Args:
screen_state: État d'écran
embedders: Dict d'embedders
output_dir: Répertoire de sortie
Returns:
StateEmbedding
"""
builder = StateEmbeddingBuilder(embedders=embedders, output_dir=output_dir)
return builder.build(screen_state)

View File

@@ -1,146 +0,0 @@
# Implémentation Capture d'Écran et Embedding Visuel - VWB
**Auteur : Dom, Alice, Kiro - 09 janvier 2026**
## Résumé
Cette documentation décrit l'implémentation des endpoints de capture d'écran et de création d'embeddings visuels pour le Visual Workflow Builder (VWB).
## Fonctionnalités Implémentées
### 1. Endpoint `/api/screen-capture` (POST)
Capture l'écran actuel et retourne l'image en base64.
**Request Body (optionnel):**
```json
{
"format": "png",
"quality": 90
}
```
**Response:**
```json
{
"success": true,
"screenshot": "base64_encoded_image...",
"width": 1920,
"height": 1080,
"timestamp": "2026-01-09T13:41:18.123456"
}
```
### 2. Endpoint `/api/visual-embedding` (POST)
Crée un embedding visuel à partir d'une capture d'écran et d'une zone sélectionnée.
**Request Body:**
```json
{
"screenshot": "base64_encoded_image...",
"boundingBox": {
"x": 100,
"y": 200,
"width": 150,
"height": 50
},
"stepId": "step_123"
}
```
**Response:**
```json
{
"success": true,
"embedding": [0.1, 0.2, ...],
"embedding_id": "emb_step_123_20260109_134118",
"dimension": 512,
"reference_image": "emb_step_123_..._ref.png",
"bounding_box": {
"x": 100,
"y": 200,
"width": 150,
"height": 50
}
}
```
### 3. Endpoint `/api/visual-embedding/<embedding_id>` (GET)
Récupère un embedding existant par son ID.
### 4. Endpoint `/api/visual-embedding/<embedding_id>/image` (GET)
Récupère l'image de référence d'un embedding.
## Architecture Technique
### Services Utilisés
1. **ScreenCapturer** (`core/capture/screen_capturer.py`)
- Capture d'écran via `mss` ou `pyautogui`
- Support multi-moniteur
- Buffer circulaire pour historique
2. **CLIPEmbedder** (`core/embedding/clip_embedder.py`)
- Modèle ViT-B/32 OpenAI
- Embeddings de dimension 512
- Exécution sur CPU pour économiser la mémoire GPU
### Stockage des Données
Les embeddings et images de référence sont stockés dans :
```
data/visual_embeddings/
├── emb_step_xxx_YYYYMMDD_HHMMSS.npy # Embedding numpy
└── emb_step_xxx_YYYYMMDD_HHMMSS_ref.png # Image de référence
```
## Intégration Frontend
Le composant `VisualSelector` (`visual_workflow_builder/frontend/src/components/VisualSelector/index.tsx`) utilise ces endpoints pour :
1. **Étape 1 - Capture** : Appel à `/api/screen-capture`
2. **Étape 2 - Sélection** : Interface canvas pour sélectionner une zone
3. **Étape 3 - Confirmation** : Appel à `/api/visual-embedding` pour créer l'embedding
## Tests
Les tests sont disponibles dans :
- `tests/integration/test_vwb_screen_capture_api.py`
### Exécution des Tests
```bash
python3 -c "
import sys
sys.path.insert(0, '.')
sys.path.insert(0, 'visual_workflow_builder/backend')
from app_lightweight import capture_screen_to_base64, create_visual_embedding
# Test capture
result = capture_screen_to_base64()
print(f'Capture: {result[\"success\"]}')
# Test embedding
if result['success']:
bbox = {'x': 100, 'y': 100, 'width': 200, 'height': 100}
emb = create_visual_embedding(result['screenshot'], bbox, 'test')
print(f'Embedding: {emb[\"success\"]}')
"
```
## Résultats de Validation
- ✅ Capture d'écran fonctionnelle (1920x1080)
- ✅ Création d'embeddings CLIP (dimension 512)
- ✅ Sauvegarde des embeddings en fichiers .npy
- ✅ Sauvegarde des images de référence en PNG
- ✅ Intégration avec le frontend VisualSelector
## Prochaines Étapes
1. Tests d'intégration avec le frontend en conditions réelles
2. Optimisation du temps de chargement du modèle CLIP
3. Ajout de la recherche par similarité dans les embeddings existants

View File

@@ -1,70 +0,0 @@
#!/usr/bin/env python3
"""
Script de démarrage du backend VWB avec environnement virtuel.
Auteur : Dom, Alice, Kiro - 09 janvier 2026
Ce script démarre le backend VWB en s'assurant que l'environnement virtuel
est correctement configuré pour les dépendances de capture d'écran.
"""
import os
import sys
import subprocess
from pathlib import Path
def main():
"""Démarre le backend VWB avec l'environnement virtuel."""
print("🚀 Démarrage du backend VWB avec environnement virtuel...")
# Répertoire racine
root_dir = Path(__file__).parent.parent
# Chemin vers l'environnement virtuel
venv_dir = root_dir / "venv_v3"
venv_python = venv_dir / "bin" / "python3"
# Script backend
backend_script = root_dir / "visual_workflow_builder" / "backend" / "app_lightweight.py"
# Vérifications
if not venv_dir.exists():
print("❌ Environnement virtuel non trouvé dans venv_v3/")
return False
if not venv_python.exists():
print("❌ Python de l'environnement virtuel non trouvé")
return False
if not backend_script.exists():
print("❌ Script backend non trouvé")
return False
# Variables d'environnement
env = os.environ.copy()
env['PYTHONPATH'] = str(root_dir)
env['PORT'] = '5002'
print(f"🐍 Python: {venv_python}")
print(f"📁 Script: {backend_script}")
print(f"🌐 Port: 5002")
print("")
try:
# Démarrer le serveur
subprocess.run([
str(venv_python),
str(backend_script)
], env=env, cwd=str(root_dir))
except KeyboardInterrupt:
print("\n🛑 Arrêt du serveur")
except Exception as e:
print(f"❌ Erreur: {e}")
return False
return True
if __name__ == '__main__':
success = main()
sys.exit(0 if success else 1)

View File

@@ -1,112 +0,0 @@
#!/usr/bin/env python3
"""
Test simple du backend VWB avec environnement virtuel.
Auteur : Dom, Alice, Kiro - 09 janvier 2026
Ce test vérifie que le backend VWB fonctionne correctement avec l'environnement virtuel.
"""
import sys
import subprocess
import time
from pathlib import Path
# Ajouter le répertoire racine au path
ROOT_DIR = Path(__file__).parent.parent.parent
sys.path.insert(0, str(ROOT_DIR))
def test_backend_direct():
"""Teste le backend directement avec l'environnement virtuel."""
print("🔍 Test direct du backend VWB...")
# Utiliser l'environnement virtuel
venv_python = ROOT_DIR / "venv_v3" / "bin" / "python3"
if not venv_python.exists():
print("❌ Environnement virtuel non trouvé")
return False
# Test des fonctions backend directement
test_script = f'''
import sys
from pathlib import Path
ROOT_DIR = Path("{ROOT_DIR}")
sys.path.insert(0, str(ROOT_DIR))
sys.path.insert(0, str(ROOT_DIR / "visual_workflow_builder" / "backend"))
try:
from app_lightweight import capture_screen_to_base64, create_visual_embedding
print("🔄 Test de capture d'écran...")
result = capture_screen_to_base64()
if result['success']:
print(f"✅ Capture réussie - {{result['width']}}x{{result['height']}}")
# Test d'embedding
print("🔄 Test d'embedding...")
bounding_box = {{'x': 100, 'y': 100, 'width': 200, 'height': 150}}
embedding_result = create_visual_embedding(
result['screenshot'],
bounding_box,
'test_backend_simple'
)
if embedding_result['success']:
print(f"✅ Embedding créé - ID: {{embedding_result['embedding_id']}}")
print("✅ BACKEND FONCTIONNE CORRECTEMENT")
else:
print(f"❌ Erreur embedding: {{embedding_result['error']}}")
else:
print(f"❌ Erreur capture: {{result['error']}}")
except Exception as e:
print(f"❌ Erreur: {{e}}")
import traceback
traceback.print_exc()
'''
try:
# Exécuter le test avec l'environnement virtuel
result = subprocess.run(
[str(venv_python), "-c", test_script],
capture_output=True,
text=True,
cwd=str(ROOT_DIR)
)
print("Sortie du test:")
print(result.stdout)
if result.stderr:
print("Erreurs:")
print(result.stderr)
return "BACKEND FONCTIONNE CORRECTEMENT" in result.stdout
except Exception as e:
print(f"❌ Erreur lors du test: {e}")
return False
def main():
"""Fonction principale de test."""
print("=" * 60)
print(" TEST BACKEND VWB SIMPLE")
print("=" * 60)
print("Auteur : Dom, Alice, Kiro - 09 janvier 2026")
print("")
success = test_backend_direct()
if success:
print("\n✅ Le backend VWB fonctionne correctement !")
else:
print("\n❌ Le backend VWB ne fonctionne pas correctement")
return success
if __name__ == '__main__':
success = main()
sys.exit(0 if success else 1)

View File

@@ -1,297 +0,0 @@
#!/usr/bin/env python3
"""
Test de la capture d'élément cible pour le Visual Workflow Builder.
Auteur : Dom, Alice, Kiro - 09 janvier 2026
Ce test vérifie que le système de capture d'élément cible fonctionne correctement
en testant les endpoints /api/screen-capture et /api/visual-embedding.
"""
import sys
import os
import time
import requests
import json
import subprocess
from pathlib import Path
# Ajouter le répertoire racine au path
ROOT_DIR = Path(__file__).parent.parent.parent
sys.path.insert(0, str(ROOT_DIR))
def start_backend_server():
"""Démarre le serveur backend VWB avec l'environnement virtuel."""
print("🚀 Démarrage du serveur backend VWB...")
# Utiliser l'environnement virtuel
venv_python = ROOT_DIR / "venv_v3" / "bin" / "python3"
backend_script = ROOT_DIR / "visual_workflow_builder" / "backend" / "app_lightweight.py"
if not venv_python.exists():
print("❌ Environnement virtuel non trouvé")
return None
if not backend_script.exists():
print("❌ Script backend non trouvé")
return None
# Variables d'environnement pour le serveur
env = os.environ.copy()
env['PYTHONPATH'] = str(ROOT_DIR)
env['PORT'] = '5002'
print(f"🐍 Utilisation de: {venv_python}")
print(f"📁 Script: {backend_script}")
# Démarrer le serveur en arrière-plan avec l'environnement virtuel
process = subprocess.Popen(
[str(venv_python), str(backend_script)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=str(ROOT_DIR),
env=env
)
# Attendre que le serveur démarre
print("⏳ Attente du démarrage du serveur...")
time.sleep(10) # Plus de temps pour l'initialisation CLIP
return process
def test_health_endpoint():
"""Teste l'endpoint de santé."""
print("\n🔍 Test de l'endpoint de santé...")
try:
response = requests.get("http://localhost:5002/health", timeout=5)
if response.status_code == 200:
data = response.json()
print(f"✅ Serveur en bonne santé - Version: {data.get('version', 'inconnue')}")
# Vérifier les fonctionnalités disponibles
features = data.get('features', {})
if features.get('screen_capture'):
print("✅ Capture d'écran disponible")
else:
print("⚠️ Capture d'écran non disponible")
if features.get('visual_embedding'):
print("✅ Embedding visuel disponible")
else:
print("⚠️ Embedding visuel non disponible")
return True
else:
print(f"❌ Erreur health check: {response.status_code}")
return False
except Exception as e:
print(f"❌ Erreur connexion serveur: {e}")
return False
def test_screen_capture_endpoint():
"""Teste l'endpoint de capture d'écran."""
print("\n📷 Test de l'endpoint de capture d'écran...")
try:
response = requests.post(
"http://localhost:5002/api/screen-capture",
json={"format": "png", "quality": 90},
timeout=15
)
if response.status_code == 200:
data = response.json()
if data.get('success'):
print(f"✅ Capture réussie - {data['width']}x{data['height']}")
print(f"📊 Taille base64: {len(data['screenshot'])} caractères")
print(f"⏰ Timestamp: {data.get('timestamp', 'N/A')}")
return data['screenshot']
else:
print(f"❌ Erreur capture: {data.get('error', 'inconnue')}")
return None
else:
print(f"❌ Erreur HTTP: {response.status_code}")
print(f"Réponse: {response.text}")
return None
except Exception as e:
print(f"❌ Erreur lors de la capture: {e}")
return None
def test_visual_embedding_endpoint(screenshot_base64):
"""Teste l'endpoint de création d'embedding visuel."""
print("\n🎯 Test de l'endpoint d'embedding visuel...")
if not screenshot_base64:
print("❌ Pas de capture d'écran disponible")
return False
try:
# Zone de test au centre de l'écran
bounding_box = {
"x": 500,
"y": 300,
"width": 200,
"height": 150
}
payload = {
"screenshot": screenshot_base64,
"boundingBox": bounding_box,
"stepId": "test_capture_element_cible"
}
response = requests.post(
"http://localhost:5002/api/visual-embedding",
json=payload,
timeout=20 # Plus de temps pour CLIP
)
if response.status_code == 200:
data = response.json()
if data.get('success'):
print(f"✅ Embedding créé - ID: {data['embedding_id']}")
print(f"📐 Dimension: {data['dimension']}")
print(f"🖼️ Image de référence: {data['reference_image']}")
print(f"📦 Zone traitée: {data['bounding_box']}")
# Vérifier que les fichiers ont été créés
embeddings_dir = ROOT_DIR / "data" / "visual_embeddings"
embedding_file = embeddings_dir / f"{data['embedding_id']}.npy"
reference_file = embeddings_dir / f"{data['embedding_id']}_ref.png"
if embedding_file.exists() and reference_file.exists():
print(f"✅ Fichiers sauvegardés correctement")
print(f" - Embedding: {embedding_file}")
print(f" - Référence: {reference_file}")
return True
else:
print(f"❌ Fichiers non créés")
return False
else:
print(f"❌ Erreur embedding: {data.get('error', 'inconnue')}")
return False
else:
print(f"❌ Erreur HTTP: {response.status_code}")
print(f"Réponse: {response.text}")
return False
except Exception as e:
print(f"❌ Erreur lors de l'embedding: {e}")
return False
def test_frontend_integration():
"""Teste l'intégration avec le frontend."""
print("\n🌐 Test d'intégration frontend...")
# Vérifier que le composant VisualSelector existe
visual_selector_path = ROOT_DIR / "visual_workflow_builder" / "frontend" / "src" / "components" / "VisualSelector" / "index.tsx"
if visual_selector_path.exists():
print("✅ Composant VisualSelector trouvé")
# Lire le contenu pour vérifier les endpoints
content = visual_selector_path.read_text()
if "/api/screen-capture" in content and "/api/visual-embedding" in content:
print("✅ Endpoints API correctement référencés dans le frontend")
# Vérifier les types TypeScript
types_path = ROOT_DIR / "visual_workflow_builder" / "frontend" / "src" / "types" / "index.ts"
if types_path.exists():
types_content = types_path.read_text()
if "VisualSelection" in types_content and "BoundingBox" in types_content:
print("✅ Types TypeScript définis correctement")
return True
else:
print("⚠️ Types TypeScript manquants")
return False
else:
print("⚠️ Fichier de types non trouvé")
return False
else:
print("❌ Endpoints API manquants dans le frontend")
return False
else:
print("❌ Composant VisualSelector non trouvé")
return False
def test_canvas_integration():
"""Teste l'intégration avec le canvas."""
print("\n🎨 Test d'intégration canvas...")
# Vérifier que le canvas peut afficher l'image
canvas_path = ROOT_DIR / "visual_workflow_builder" / "frontend" / "src" / "components" / "Canvas"
if canvas_path.exists():
print("✅ Répertoire Canvas trouvé")
# Vérifier les fichiers du canvas
step_node_path = canvas_path / "StepNode.tsx"
if step_node_path.exists():
print("✅ Composant StepNode trouvé")
return True
else:
print("⚠️ Composant StepNode non trouvé")
return False
else:
print("❌ Répertoire Canvas non trouvé")
return False
def main():
"""Fonction principale de test."""
print("=" * 60)
print(" TEST CAPTURE D'ÉLÉMENT CIBLE - VWB")
print("=" * 60)
print("Auteur : Dom, Alice, Kiro - 09 janvier 2026")
print("")
# Démarrer le serveur backend
server_process = start_backend_server()
if not server_process:
print("❌ Impossible de démarrer le serveur backend")
return False
try:
# Test 1: Health check
if not test_health_endpoint():
return False
# Test 2: Capture d'écran
screenshot = test_screen_capture_endpoint()
if not screenshot:
return False
# Test 3: Embedding visuel
if not test_visual_embedding_endpoint(screenshot):
return False
# Test 4: Intégration frontend
if not test_frontend_integration():
return False
# Test 5: Intégration canvas
if not test_canvas_integration():
return False
print("\n" + "=" * 60)
print("🎉 TOUS LES TESTS SONT PASSÉS AVEC SUCCÈS !")
print("✅ La capture d'élément cible fonctionne correctement")
print("✅ Backend et frontend intégrés")
print("✅ Fichiers d'embedding sauvegardés")
print("=" * 60)
return True
finally:
# Arrêter le serveur
if server_process:
print("\n🛑 Arrêt du serveur backend...")
server_process.terminate()
server_process.wait()
if __name__ == '__main__':
success = main()
sys.exit(0 if success else 1)

View File

@@ -1,154 +0,0 @@
#!/usr/bin/env python3
"""
Test de debug du backend VWB pour identifier le problème de capture.
Auteur : Dom, Alice, Kiro - 09 janvier 2026
Ce test examine les logs du serveur pour identifier pourquoi la capture échoue.
"""
import sys
import os
import time
import requests
import subprocess
from pathlib import Path
# Ajouter le répertoire racine au path
ROOT_DIR = Path(__file__).parent.parent.parent
sys.path.insert(0, str(ROOT_DIR))
def start_backend_server_debug():
"""Démarre le serveur backend VWB en mode debug."""
print("🚀 Démarrage du serveur backend VWB en mode debug...")
# Utiliser l'environnement virtuel
venv_python = ROOT_DIR / "venv_v3" / "bin" / "python3"
backend_script = ROOT_DIR / "visual_workflow_builder" / "backend" / "app_lightweight.py"
# Variables d'environnement pour le serveur
env = os.environ.copy()
env['PYTHONPATH'] = str(ROOT_DIR)
env['PORT'] = '5002'
print(f"🐍 Utilisation de: {venv_python}")
print(f"📁 Script: {backend_script}")
# Démarrer le serveur en mode interactif pour voir les logs
process = subprocess.Popen(
[str(venv_python), str(backend_script)],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, # Rediriger stderr vers stdout
cwd=str(ROOT_DIR),
env=env,
text=True,
bufsize=1,
universal_newlines=True
)
# Attendre que le serveur démarre et afficher les logs
print("⏳ Attente du démarrage du serveur...")
time.sleep(3)
# Lire les logs de démarrage
print("\n📋 Logs de démarrage du serveur:")
print("-" * 40)
# Lire quelques lignes de sortie
for i in range(20): # Lire les 20 premières lignes
try:
line = process.stdout.readline()
if line:
print(f"LOG: {line.strip()}")
else:
break
except:
break
print("-" * 40)
return process
def test_capture_with_logs(server_process):
"""Teste la capture en surveillant les logs."""
print("\n📷 Test de capture avec surveillance des logs...")
# Faire une requête de capture
try:
print("🔄 Envoi de la requête de capture...")
response = requests.post(
"http://localhost:5002/api/screen-capture",
json={"format": "png", "quality": 90},
timeout=15
)
print(f"📊 Statut de réponse: {response.status_code}")
# Lire les logs pendant la requête
print("\n📋 Logs pendant la capture:")
print("-" * 40)
# Lire quelques lignes supplémentaires
for i in range(10):
try:
line = server_process.stdout.readline()
if line:
print(f"LOG: {line.strip()}")
else:
break
except:
break
print("-" * 40)
if response.status_code == 200:
data = response.json()
if data.get('success'):
print(f"✅ Capture réussie - {data['width']}x{data['height']}")
return True
else:
print(f"❌ Erreur capture: {data.get('error', 'inconnue')}")
return False
else:
print(f"❌ Erreur HTTP: {response.status_code}")
print(f"Réponse: {response.text}")
return False
except Exception as e:
print(f"❌ Erreur lors de la capture: {e}")
return False
def main():
"""Fonction principale de test."""
print("=" * 60)
print(" TEST DEBUG BACKEND VWB")
print("=" * 60)
print("Auteur : Dom, Alice, Kiro - 09 janvier 2026")
print("")
# Démarrer le serveur backend
server_process = start_backend_server_debug()
if not server_process:
print("❌ Impossible de démarrer le serveur backend")
return False
try:
# Attendre un peu plus pour le démarrage complet
time.sleep(5)
# Tester la capture avec logs
success = test_capture_with_logs(server_process)
return success
finally:
# Arrêter le serveur
if server_process:
print("\n🛑 Arrêt du serveur backend...")
server_process.terminate()
server_process.wait()
if __name__ == '__main__':
success = main()
sys.exit(0 if success else 1)

View File

@@ -1,257 +0,0 @@
#!/usr/bin/env python3
"""
Tests d'intégration pour l'API de capture d'écran et d'embedding visuel du VWB.
Auteur : Dom, Alice, Kiro - 09 janvier 2026
Ces tests vérifient que les endpoints /api/screen-capture et /api/visual-embedding
fonctionnent correctement avec le système de capture réel.
"""
import pytest
import sys
import os
from pathlib import Path
# Ajouter le répertoire racine au path
ROOT_DIR = Path(__file__).parent.parent.parent
sys.path.insert(0, str(ROOT_DIR))
class TestScreenCaptureService:
"""Tests pour le service de capture d'écran."""
def test_screen_capturer_import(self):
"""Vérifie que le ScreenCapturer peut être importé."""
try:
from core.capture import ScreenCapturer
assert ScreenCapturer is not None
except ImportError as e:
pytest.skip(f"ScreenCapturer non disponible: {e}")
def test_screen_capturer_initialization(self):
"""Vérifie que le ScreenCapturer peut être initialisé."""
try:
from core.capture import ScreenCapturer
capturer = ScreenCapturer(buffer_size=2, detect_changes=False)
assert capturer is not None
assert capturer.method in ["mss", "pyautogui"]
except ImportError as e:
pytest.skip(f"ScreenCapturer non disponible: {e}")
except Exception as e:
# Peut échouer sur un serveur sans écran
pytest.skip(f"Capture d'écran non disponible: {e}")
def test_screen_capture_returns_array(self):
"""Vérifie que la capture retourne un tableau numpy valide."""
try:
from core.capture import ScreenCapturer
import numpy as np
capturer = ScreenCapturer(buffer_size=2, detect_changes=False)
img = capturer.capture()
if img is None:
pytest.skip("Capture d'écran non disponible (pas d'écran)")
assert isinstance(img, np.ndarray)
assert len(img.shape) == 3 # (H, W, C)
assert img.shape[2] == 3 # RGB
assert img.shape[0] > 0 # Hauteur > 0
assert img.shape[1] > 0 # Largeur > 0
except ImportError as e:
pytest.skip(f"Dépendances non disponibles: {e}")
except Exception as e:
pytest.skip(f"Capture d'écran non disponible: {e}")
class TestCLIPEmbedderService:
"""Tests pour le service d'embedding CLIP."""
def test_clip_embedder_import(self):
"""Vérifie que le CLIPEmbedder peut être importé."""
try:
from core.embedding import create_clip_embedder
assert create_clip_embedder is not None
except ImportError as e:
pytest.skip(f"CLIPEmbedder non disponible: {e}")
def test_clip_embedder_initialization(self):
"""Vérifie que le CLIPEmbedder peut être initialisé."""
try:
from core.embedding import create_clip_embedder
embedder = create_clip_embedder(device="cpu")
assert embedder is not None
assert embedder.get_dimension() > 0
except ImportError as e:
pytest.skip(f"CLIPEmbedder non disponible: {e}")
except Exception as e:
pytest.skip(f"Initialisation CLIP échouée: {e}")
def test_clip_embedding_dimension(self):
"""Vérifie que les embeddings ont la bonne dimension."""
try:
from core.embedding import create_clip_embedder
from PIL import Image
import numpy as np
embedder = create_clip_embedder(device="cpu")
# Créer une image de test
test_image = Image.fromarray(
np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
)
embedding = embedder.embed_image(test_image)
assert isinstance(embedding, np.ndarray)
assert len(embedding.shape) == 1
assert embedding.shape[0] == embedder.get_dimension()
except ImportError as e:
pytest.skip(f"Dépendances non disponibles: {e}")
except Exception as e:
pytest.skip(f"Embedding échoué: {e}")
class TestBackendFunctions:
"""Tests pour les fonctions du backend VWB."""
def test_capture_screen_to_base64_function(self):
"""Vérifie la fonction capture_screen_to_base64."""
try:
sys.path.insert(0, str(ROOT_DIR / "visual_workflow_builder" / "backend"))
from app_lightweight import capture_screen_to_base64
result = capture_screen_to_base64()
assert isinstance(result, dict)
assert 'success' in result
if result['success']:
assert 'screenshot' in result
assert 'width' in result
assert 'height' in result
assert isinstance(result['screenshot'], str)
assert len(result['screenshot']) > 0
else:
# Peut échouer si pas d'écran disponible
assert 'error' in result
except ImportError as e:
pytest.skip(f"Backend non disponible: {e}")
except Exception as e:
pytest.skip(f"Test échoué: {e}")
def test_create_visual_embedding_function(self):
"""Vérifie la fonction create_visual_embedding."""
try:
import base64
from PIL import Image
import numpy as np
import io
sys.path.insert(0, str(ROOT_DIR / "visual_workflow_builder" / "backend"))
from app_lightweight import create_visual_embedding
# Créer une image de test en base64
test_image = Image.fromarray(
np.random.randint(0, 255, (200, 200, 3), dtype=np.uint8)
)
buffer = io.BytesIO()
test_image.save(buffer, format='PNG')
buffer.seek(0)
screenshot_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
# Zone de sélection
bounding_box = {
'x': 50,
'y': 50,
'width': 100,
'height': 100
}
result = create_visual_embedding(screenshot_base64, bounding_box, "test_step")
assert isinstance(result, dict)
assert 'success' in result
if result['success']:
assert 'embedding' in result
assert 'embedding_id' in result
assert 'dimension' in result
assert isinstance(result['embedding'], list)
assert len(result['embedding']) > 0
else:
# Peut échouer si CLIP non disponible
assert 'error' in result
except ImportError as e:
pytest.skip(f"Dépendances non disponibles: {e}")
except Exception as e:
pytest.skip(f"Test échoué: {e}")
class TestAPIEndpointsStructure:
"""Tests pour la structure des endpoints API."""
def test_backend_module_loads(self):
"""Vérifie que le module backend peut être chargé."""
try:
sys.path.insert(0, str(ROOT_DIR / "visual_workflow_builder" / "backend"))
import app_lightweight
assert app_lightweight is not None
except ImportError as e:
pytest.fail(f"Impossible de charger le backend: {e}")
def test_workflow_database_class_exists(self):
"""Vérifie que la classe WorkflowDatabase existe."""
try:
sys.path.insert(0, str(ROOT_DIR / "visual_workflow_builder" / "backend"))
from app_lightweight import WorkflowDatabase
assert WorkflowDatabase is not None
db = WorkflowDatabase()
assert db is not None
except ImportError as e:
pytest.fail(f"WorkflowDatabase non disponible: {e}")
def test_simple_workflow_class_exists(self):
"""Vérifie que la classe SimpleWorkflow existe."""
try:
sys.path.insert(0, str(ROOT_DIR / "visual_workflow_builder" / "backend"))
from app_lightweight import SimpleWorkflow
assert SimpleWorkflow is not None
workflow = SimpleWorkflow(
id="test_wf",
name="Test Workflow",
description="Description de test"
)
assert workflow.id == "test_wf"
assert workflow.name == "Test Workflow"
except ImportError as e:
pytest.fail(f"SimpleWorkflow non disponible: {e}")
class TestDataDirectory:
"""Tests pour la structure des répertoires de données."""
def test_visual_embeddings_directory_creation(self):
"""Vérifie que le répertoire visual_embeddings peut être créé."""
embeddings_dir = ROOT_DIR / "data" / "visual_embeddings"
embeddings_dir.mkdir(parents=True, exist_ok=True)
assert embeddings_dir.exists()
assert embeddings_dir.is_dir()
def test_workflows_directory_creation(self):
"""Vérifie que le répertoire workflows peut être créé."""
workflows_dir = ROOT_DIR / "data" / "workflows"
workflows_dir.mkdir(parents=True, exist_ok=True)
assert workflows_dir.exists()
assert workflows_dir.is_dir()
if __name__ == '__main__':
pytest.main([__file__, '-v', '--tb=short'])

View File

@@ -1,753 +0,0 @@
#!/usr/bin/env python3
"""
Visual Workflow Builder - Backend Flask Application (Version Allégée)
Auteur : Dom, Alice, Kiro - 09 janvier 2026
Version optimisée pour un démarrage rapide avec uniquement les fonctionnalités essentielles.
Cette version évite les imports lourds et les dépendances optionnelles.
Fonctionnalités :
- API REST pour la gestion des workflows
- Capture d'écran via ScreenCapturer (core/capture)
- Création d'embeddings visuels via CLIPEmbedder (core/embedding)
"""
import json
import os
import sys
import base64
import io
from pathlib import Path
from datetime import datetime
from typing import Dict, Any, List, Optional
# Ajouter le répertoire racine au path pour les imports core
ROOT_DIR = Path(__file__).parent.parent.parent
sys.path.insert(0, str(ROOT_DIR))
sys.path.insert(0, str(Path(__file__).parent))
# Import minimal sans dépendances lourdes
try:
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import socketserver
USE_FLASK = False
print("⚡ Mode serveur HTTP natif (sans Flask)")
except ImportError:
USE_FLASK = True
print("🔄 Tentative d'utilisation de Flask...")
# ============================================================================
# Services de capture d'écran et d'embedding
# ============================================================================
# Instance globale du capturer (initialisée à la demande)
_screen_capturer = None
_clip_embedder = None
def get_screen_capturer():
"""
Obtenir l'instance du ScreenCapturer (initialisation paresseuse).
Returns:
ScreenCapturer ou None si non disponible
"""
global _screen_capturer
if _screen_capturer is None:
try:
# Vérifier les dépendances de capture d'écran
try:
import mss
print("✅ mss disponible")
except ImportError:
print("❌ mss non disponible")
try:
import pyautogui
print("✅ pyautogui disponible")
except ImportError:
print("❌ pyautogui non disponible")
from core.capture import ScreenCapturer
_screen_capturer = ScreenCapturer(buffer_size=5, detect_changes=False)
print(f"✅ ScreenCapturer initialisé avec succès - méthode: {_screen_capturer.method}")
except ImportError as e:
print(f"⚠️ ScreenCapturer non disponible: {e}")
return None
except Exception as e:
print(f"❌ Erreur initialisation ScreenCapturer: {e}")
return None
return _screen_capturer
def get_clip_embedder():
"""
Obtenir l'instance du CLIPEmbedder (initialisation paresseuse).
Returns:
CLIPEmbedder ou None si non disponible
"""
global _clip_embedder
if _clip_embedder is None:
try:
from core.embedding import create_clip_embedder
_clip_embedder = create_clip_embedder(device="cpu")
print("✅ CLIPEmbedder initialisé avec succès")
except ImportError as e:
print(f"⚠️ CLIPEmbedder non disponible: {e}")
return None
except Exception as e:
print(f"❌ Erreur initialisation CLIPEmbedder: {e}")
return None
return _clip_embedder
def capture_screen_to_base64() -> Dict[str, Any]:
"""
Capture l'écran et retourne l'image en base64.
Returns:
Dict avec 'success', 'screenshot' (base64), 'width', 'height', ou 'error'
"""
capturer = get_screen_capturer()
if capturer is None:
return {
'success': False,
'error': 'Service de capture d\'écran non disponible'
}
try:
from PIL import Image
import numpy as np
# Capturer l'écran
img_array = capturer.capture()
if img_array is None:
return {
'success': False,
'error': 'Échec de la capture d\'écran'
}
# Convertir en PIL Image
pil_image = Image.fromarray(img_array)
# Convertir en base64
buffer = io.BytesIO()
pil_image.save(buffer, format='PNG', optimize=True)
buffer.seek(0)
screenshot_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
return {
'success': True,
'screenshot': screenshot_base64,
'width': pil_image.width,
'height': pil_image.height,
'timestamp': datetime.now().isoformat()
}
except Exception as e:
return {
'success': False,
'error': f'Erreur lors de la capture: {str(e)}'
}
def create_visual_embedding(screenshot_base64: str, bounding_box: Dict[str, int], step_id: str) -> Dict[str, Any]:
"""
Crée un embedding visuel à partir d'une capture d'écran et d'une zone sélectionnée.
Args:
screenshot_base64: Image en base64
bounding_box: Zone sélectionnée {'x', 'y', 'width', 'height'}
step_id: Identifiant de l'étape
Returns:
Dict avec 'success', 'embedding', 'embedding_id', ou 'error'
"""
embedder = get_clip_embedder()
if embedder is None:
return {
'success': False,
'error': 'Service d\'embedding non disponible'
}
try:
from PIL import Image
import numpy as np
# Décoder l'image base64
image_data = base64.b64decode(screenshot_base64)
pil_image = Image.open(io.BytesIO(image_data))
# Extraire la zone sélectionnée
x = bounding_box.get('x', 0)
y = bounding_box.get('y', 0)
width = bounding_box.get('width', 100)
height = bounding_box.get('height', 100)
# Valider les coordonnées
x = max(0, min(x, pil_image.width - 1))
y = max(0, min(y, pil_image.height - 1))
width = max(10, min(width, pil_image.width - x))
height = max(10, min(height, pil_image.height - y))
# Découper la zone
cropped_image = pil_image.crop((x, y, x + width, y + height))
# Créer l'embedding
embedding = embedder.embed_image(cropped_image)
# Générer un ID unique pour l'embedding
embedding_id = f"emb_{step_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
# Sauvegarder l'embedding et l'image de référence
embeddings_dir = ROOT_DIR / "data" / "visual_embeddings"
embeddings_dir.mkdir(parents=True, exist_ok=True)
# Sauvegarder l'embedding en numpy
embedding_path = embeddings_dir / f"{embedding_id}.npy"
np.save(str(embedding_path), embedding)
# Sauvegarder l'image de référence
reference_path = embeddings_dir / f"{embedding_id}_ref.png"
cropped_image.save(str(reference_path))
return {
'success': True,
'embedding': embedding.tolist(),
'embedding_id': embedding_id,
'dimension': len(embedding),
'reference_image': f"{embedding_id}_ref.png",
'bounding_box': {
'x': x,
'y': y,
'width': width,
'height': height
}
}
except Exception as e:
return {
'success': False,
'error': f'Erreur lors de la création de l\'embedding: {str(e)}'
}
class WorkflowHandler(BaseHTTPRequestHandler):
"""Gestionnaire HTTP simple pour les workflows."""
def __init__(self, *args, **kwargs):
self.workflows_db = WorkflowDatabase()
super().__init__(*args, **kwargs)
def do_GET(self):
"""Gère les requêtes GET."""
parsed_path = urlparse(self.path)
path = parsed_path.path
# Headers CORS
self.send_cors_headers()
if path == '/health':
self.send_health_check()
elif path == '/':
self.send_index()
elif path.startswith('/api/workflows'):
self.handle_workflows_get(path)
else:
self.send_error(404, "Not Found")
def do_POST(self):
"""Gère les requêtes POST."""
parsed_path = urlparse(self.path)
path = parsed_path.path
self.send_cors_headers()
if path.startswith('/api/workflows'):
self.handle_workflows_post(path)
else:
self.send_error(404, "Not Found")
def do_OPTIONS(self):
"""Gère les requêtes OPTIONS pour CORS."""
self.send_cors_headers()
self.send_response(200)
self.end_headers()
def send_cors_headers(self):
"""Envoie les headers CORS."""
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
def send_json_response(self, data: Any, status_code: int = 200):
"""Envoie une réponse JSON."""
self.send_response(status_code)
self.send_header('Content-Type', 'application/json')
self.send_cors_headers()
self.end_headers()
json_data = json.dumps(data, ensure_ascii=False, indent=2)
self.wfile.write(json_data.encode('utf-8'))
def send_health_check(self):
"""Endpoint de santé."""
self.send_json_response({
'status': 'healthy',
'version': '1.0.0-lightweight',
'mode': 'native-http'
})
def send_index(self):
"""Page d'accueil."""
self.send_json_response({
'message': 'Visual Workflow Builder Backend (Version Allégée)',
'version': '1.0.0-lightweight',
'mode': 'native-http',
'endpoints': ['/health', '/api/workflows']
})
def handle_workflows_get(self, path: str):
"""Gère les GET sur /api/workflows."""
if path == '/api/workflows' or path == '/api/workflows/':
# Liste des workflows
try:
workflows = self.workflows_db.list_workflows()
self.send_json_response([w.to_dict() for w in workflows])
except Exception as e:
self.send_json_response({'error': str(e)}, 500)
else:
# Workflow spécifique
workflow_id = path.split('/')[-1]
try:
workflow = self.workflows_db.get_workflow(workflow_id)
if workflow:
self.send_json_response(workflow.to_dict())
else:
self.send_json_response({'error': 'Workflow not found'}, 404)
except Exception as e:
self.send_json_response({'error': str(e)}, 500)
def handle_workflows_post(self, path: str):
"""Gère les POST sur /api/workflows."""
try:
content_length = int(self.headers.get('Content-Length', 0))
if content_length > 0:
post_data = self.rfile.read(content_length)
data = json.loads(post_data.decode('utf-8'))
else:
data = {}
if path == '/api/workflows' or path == '/api/workflows/':
# Créer un nouveau workflow
workflow = self.workflows_db.create_workflow(data)
self.send_json_response(workflow.to_dict(), 201)
else:
self.send_json_response({'error': 'Method not allowed'}, 405)
except json.JSONDecodeError:
self.send_json_response({'error': 'Invalid JSON'}, 400)
except Exception as e:
self.send_json_response({'error': str(e)}, 500)
class SimpleWorkflow:
"""Modèle de workflow simplifié."""
def __init__(self, id: str, name: str, description: str = "", created_by: str = "unknown"):
self.id = id
self.name = name
self.description = description
self.created_by = created_by
self.created_at = datetime.now().isoformat()
self.updated_at = self.created_at
self.nodes = []
self.edges = []
self.variables = []
self.settings = {}
self.tags = []
self.category = "default"
self.is_template = False
def to_dict(self) -> Dict[str, Any]:
"""Convertit en dictionnaire."""
return {
'id': self.id,
'name': self.name,
'description': self.description,
'created_by': self.created_by,
'created_at': self.created_at,
'updated_at': self.updated_at,
'nodes': self.nodes,
'edges': self.edges,
'variables': self.variables,
'settings': self.settings,
'tags': self.tags,
'category': self.category,
'is_template': self.is_template
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'SimpleWorkflow':
"""Crée depuis un dictionnaire."""
workflow = cls(
id=data.get('id', f"wf_{datetime.now().strftime('%Y%m%d_%H%M%S')}"),
name=data.get('name', 'Sans titre'),
description=data.get('description', ''),
created_by=data.get('created_by', 'unknown')
)
workflow.nodes = data.get('nodes', [])
workflow.edges = data.get('edges', [])
workflow.variables = data.get('variables', [])
workflow.settings = data.get('settings', {})
workflow.tags = data.get('tags', [])
workflow.category = data.get('category', 'default')
workflow.is_template = data.get('is_template', False)
return workflow
class WorkflowDatabase:
"""Base de données simple pour les workflows."""
def __init__(self):
self.data_dir = Path("../../data/workflows")
self.data_dir.mkdir(parents=True, exist_ok=True)
print(f"📁 Base de données: {self.data_dir.absolute()}")
def _get_file_path(self, workflow_id: str) -> Path:
"""Retourne le chemin du fichier pour un workflow."""
safe_id = "".join(c for c in workflow_id if c.isalnum() or c in ("_", "-"))
return self.data_dir / f"{safe_id}.json"
def create_workflow(self, data: Dict[str, Any]) -> SimpleWorkflow:
"""Crée un nouveau workflow."""
if 'name' not in data:
raise ValueError("Le nom est requis")
workflow = SimpleWorkflow.from_dict(data)
self.save_workflow(workflow)
return workflow
def save_workflow(self, workflow: SimpleWorkflow):
"""Sauvegarde un workflow."""
file_path = self._get_file_path(workflow.id)
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(workflow.to_dict(), f, ensure_ascii=False, indent=2)
def get_workflow(self, workflow_id: str) -> Optional[SimpleWorkflow]:
"""Récupère un workflow par ID."""
file_path = self._get_file_path(workflow_id)
if not file_path.exists():
return None
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return SimpleWorkflow.from_dict(data)
except Exception as e:
print(f"Erreur lecture workflow {workflow_id}: {e}")
return None
def list_workflows(self) -> List[SimpleWorkflow]:
"""Liste tous les workflows."""
workflows = []
for file_path in self.data_dir.glob("*.json"):
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
workflows.append(SimpleWorkflow.from_dict(data))
except Exception as e:
print(f"Erreur lecture {file_path}: {e}")
return workflows
def start_native_server(port: int = 5002):
"""Démarre le serveur HTTP natif."""
print(f"🚀 Démarrage du serveur natif sur le port {port}")
print(f"🌐 URL: http://localhost:{port}")
print(f"❤️ Health check: http://localhost:{port}/health")
print(f"📋 API Workflows: http://localhost:{port}/api/workflows")
print("")
print("Appuyez sur Ctrl+C pour arrêter")
try:
with socketserver.TCPServer(("", port), WorkflowHandler) as httpd:
httpd.serve_forever()
except KeyboardInterrupt:
print("\n🛑 Arrêt du serveur")
except Exception as e:
print(f"❌ Erreur serveur: {e}")
def start_flask_server(port: int = 5002):
"""Démarre le serveur Flask si disponible."""
try:
from flask import Flask, jsonify, request
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
db = WorkflowDatabase()
@app.route('/health')
@app.route('/api/health')
def health_check():
return jsonify({
'status': 'healthy',
'version': '1.0.0-lightweight',
'mode': 'flask',
'features': {
'screen_capture': get_screen_capturer() is not None,
'visual_embedding': get_clip_embedder() is not None
}
})
@app.route('/')
def index():
return jsonify({
'message': 'Visual Workflow Builder Backend (Version Allégée)',
'version': '1.0.0-lightweight',
'mode': 'flask',
'endpoints': [
'/health',
'/api/workflows',
'/api/screen-capture',
'/api/visual-embedding'
]
})
@app.route('/api/workflows', methods=['GET'])
def list_workflows():
try:
workflows = db.list_workflows()
return jsonify([w.to_dict() for w in workflows])
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/workflows', methods=['POST'])
def create_workflow():
try:
data = request.get_json() or {}
workflow = db.create_workflow(data)
return jsonify(workflow.to_dict()), 201
except Exception as e:
return jsonify({'error': str(e)}), 400
@app.route('/api/workflows/<workflow_id>', methods=['GET'])
def get_workflow(workflow_id):
try:
workflow = db.get_workflow(workflow_id)
if workflow:
return jsonify(workflow.to_dict())
else:
return jsonify({'error': 'Workflow not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
# ====================================================================
# Endpoints de capture d'écran et d'embedding visuel
# ====================================================================
@app.route('/api/screen-capture', methods=['POST'])
def screen_capture():
"""
Capture l'écran actuel et retourne l'image en base64.
Request Body (optionnel):
{
"format": "png", // Format de l'image (png par défaut)
"quality": 90 // Qualité (non utilisé pour PNG)
}
Response:
{
"success": true,
"screenshot": "base64_encoded_image",
"width": 1920,
"height": 1080,
"timestamp": "2026-01-09T..."
}
"""
try:
result = capture_screen_to_base64()
if result['success']:
return jsonify(result)
else:
return jsonify(result), 500
except Exception as e:
return jsonify({
'success': False,
'error': f'Erreur serveur: {str(e)}'
}), 500
@app.route('/api/visual-embedding', methods=['POST'])
def visual_embedding():
"""
Crée un embedding visuel à partir d'une capture d'écran et d'une zone sélectionnée.
Request Body:
{
"screenshot": "base64_encoded_image",
"boundingBox": {
"x": 100,
"y": 200,
"width": 150,
"height": 50
},
"stepId": "step_123"
}
Response:
{
"success": true,
"embedding": [0.1, 0.2, ...],
"embedding_id": "emb_step_123_20260109_...",
"dimension": 512,
"reference_image": "emb_step_123_..._ref.png",
"bounding_box": {...}
}
"""
try:
data = request.get_json()
if not data:
return jsonify({
'success': False,
'error': 'Corps de requête JSON requis'
}), 400
# Valider les paramètres requis
screenshot = data.get('screenshot')
bounding_box = data.get('boundingBox')
step_id = data.get('stepId', 'unknown')
if not screenshot:
return jsonify({
'success': False,
'error': 'Paramètre "screenshot" requis'
}), 400
if not bounding_box:
return jsonify({
'success': False,
'error': 'Paramètre "boundingBox" requis'
}), 400
# Créer l'embedding
result = create_visual_embedding(screenshot, bounding_box, step_id)
if result['success']:
return jsonify(result)
else:
return jsonify(result), 500
except Exception as e:
return jsonify({
'success': False,
'error': f'Erreur serveur: {str(e)}'
}), 500
@app.route('/api/visual-embedding/<embedding_id>', methods=['GET'])
def get_visual_embedding(embedding_id):
"""
Récupère un embedding visuel existant par son ID.
Response:
{
"success": true,
"embedding_id": "emb_...",
"embedding": [0.1, 0.2, ...],
"reference_image_url": "/api/visual-embedding/emb_.../image"
}
"""
try:
import numpy as np
embeddings_dir = ROOT_DIR / "data" / "visual_embeddings"
embedding_path = embeddings_dir / f"{embedding_id}.npy"
if not embedding_path.exists():
return jsonify({
'success': False,
'error': f'Embedding "{embedding_id}" non trouvé'
}), 404
embedding = np.load(str(embedding_path))
return jsonify({
'success': True,
'embedding_id': embedding_id,
'embedding': embedding.tolist(),
'dimension': len(embedding),
'reference_image_url': f'/api/visual-embedding/{embedding_id}/image'
})
except Exception as e:
return jsonify({
'success': False,
'error': f'Erreur: {str(e)}'
}), 500
@app.route('/api/visual-embedding/<embedding_id>/image', methods=['GET'])
def get_embedding_reference_image(embedding_id):
"""
Récupère l'image de référence d'un embedding.
"""
try:
from flask import send_file
embeddings_dir = ROOT_DIR / "data" / "visual_embeddings"
image_path = embeddings_dir / f"{embedding_id}_ref.png"
if not image_path.exists():
return jsonify({
'success': False,
'error': f'Image de référence non trouvée'
}), 404
return send_file(str(image_path), mimetype='image/png')
except Exception as e:
return jsonify({
'success': False,
'error': f'Erreur: {str(e)}'
}), 500
print(f"🚀 Démarrage du serveur Flask sur le port {port}")
print(f"🌐 URL: http://localhost:{port}")
print(f"❤️ Health check: http://localhost:{port}/health")
print(f"📋 API Workflows: http://localhost:{port}/api/workflows")
print(f"📷 API Capture: http://localhost:{port}/api/screen-capture")
print(f"🎯 API Embedding: http://localhost:{port}/api/visual-embedding")
app.run(host='0.0.0.0', port=port, debug=False)
except ImportError as e:
print(f"❌ Flask non disponible: {e}")
print("🔄 Basculement vers le serveur natif...")
start_native_server(port)
def main():
"""Fonction principale."""
print("=" * 60)
print(" VISUAL WORKFLOW BUILDER - BACKEND ALLÉGÉ")
print("=" * 60)
print("Auteur : Dom, Alice, Kiro - 08 janvier 2026")
print("")
# Déterminer le port
port = int(os.getenv('PORT', 5002))
# Vérifier les dépendances
try:
import flask
import flask_cors
print("✅ Flask disponible - utilisation du mode Flask")
start_flask_server(port)
except ImportError:
print("⚡ Flask non disponible - utilisation du serveur natif")
start_native_server(port)
if __name__ == '__main__':
main()

View File

@@ -1,299 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Service de Capture d'Écran Réelle - RPA Vision V3
Auteur : Dom, Alice, Kiro - 8 janvier 2026
Service pour capturer l'écran réel de l'utilisateur et détecter les éléments UI.
"""
import cv2
import numpy as np
import mss
import base64
import io
from PIL import Image
from typing import Dict, List, Tuple, Optional
import threading
import time
import logging
# Import des modules RPA Vision V3 pour la détection UI
import sys
import os
# Ajouter le chemin vers le répertoire racine du projet
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))
if project_root not in sys.path:
sys.path.insert(0, project_root)
try:
from core.detection.ui_detector import UIDetector
UI_DETECTOR_AVAILABLE = True
except ImportError as e:
print(f"Warning: UIDetector non disponible: {e}")
UI_DETECTOR_AVAILABLE = False
UIDetector = None
try:
from core.models.screen_state import ScreenState, UIElement
SCREEN_STATE_AVAILABLE = True
except ImportError as e:
print(f"Warning: ScreenState non disponible: {e}")
SCREEN_STATE_AVAILABLE = False
ScreenState = None
UIElement = None
logger = logging.getLogger(__name__)
class RealScreenCaptureService:
"""
Service de capture d'écran réelle avec détection d'éléments UI
"""
def __init__(self):
self.is_capturing = False
self.capture_thread = None
self.current_screenshot = None
self.detected_elements = []
# Initialiser le détecteur UI si disponible
if UI_DETECTOR_AVAILABLE:
self.ui_detector = UIDetector()
else:
self.ui_detector = None
print("Warning: UIDetector non disponible - détection d'éléments désactivée")
self.capture_interval = 1.0 # 1 seconde par défaut
self.monitors = []
self.selected_monitor = 0
# Initialiser MSS pour la capture d'écran
try:
# Utiliser MSS temporairement pour détecter les moniteurs
with mss.mss() as sct:
self.monitors = sct.monitors
logger.info(f"Détecté {len(self.monitors)} moniteurs")
for i, monitor in enumerate(self.monitors):
logger.info(f"Moniteur {i}: {monitor}")
except Exception as e:
logger.error(f"Erreur lors de la détection des moniteurs: {e}")
self.monitors = [{"top": 0, "left": 0, "width": 1920, "height": 1080}]
def _detect_monitors(self):
"""Détecte les moniteurs disponibles"""
try:
self.monitors = self.sct.monitors
logger.info(f"Détecté {len(self.monitors)} moniteurs")
for i, monitor in enumerate(self.monitors):
logger.info(f"Moniteur {i}: {monitor}")
except Exception as e:
logger.error(f"Erreur lors de la détection des moniteurs: {e}")
self.monitors = [{"top": 0, "left": 0, "width": 1920, "height": 1080}]
def get_monitors(self) -> List[Dict]:
"""Retourne la liste des moniteurs disponibles"""
return [
{
"id": i,
"width": monitor.get("width", 0),
"height": monitor.get("height", 0),
"top": monitor.get("top", 0),
"left": monitor.get("left", 0)
}
for i, monitor in enumerate(self.monitors)
]
def select_monitor(self, monitor_id: int) -> bool:
"""Sélectionne le moniteur à capturer"""
if 0 <= monitor_id < len(self.monitors):
self.selected_monitor = monitor_id
logger.info(f"Moniteur sélectionné: {monitor_id}")
return True
return False
def start_capture(self, interval: float = 1.0) -> bool:
"""Démarre la capture d'écran en temps réel"""
if self.is_capturing:
logger.warning("Capture déjà en cours")
return False
self.capture_interval = interval
self.is_capturing = True
# Démarrer le thread de capture
self.capture_thread = threading.Thread(target=self._capture_loop, daemon=True)
self.capture_thread.start()
logger.info(f"Capture démarrée (intervalle: {interval}s)")
return True
def stop_capture(self) -> bool:
"""Arrête la capture d'écran"""
if not self.is_capturing:
return False
self.is_capturing = False
if self.capture_thread and self.capture_thread.is_alive():
self.capture_thread.join(timeout=2.0)
logger.info("Capture arrêtée")
return True
def _capture_loop(self):
"""Boucle principale de capture avec MSS local au thread"""
# Créer une instance MSS locale au thread pour éviter les problèmes de threading
try:
with mss.mss() as sct_local:
while self.is_capturing:
try:
# Capturer l'écran avec l'instance locale
screenshot = self._capture_screen_with_sct(sct_local)
if screenshot is not None:
self.current_screenshot = screenshot
# Détecter les éléments UI
if UI_DETECTOR_AVAILABLE and self.ui_detector:
self._detect_ui_elements(screenshot)
# Attendre avant la prochaine capture
time.sleep(self.capture_interval)
except Exception as e:
logger.error(f"Erreur dans la boucle de capture: {e}")
time.sleep(1.0) # Attendre avant de réessayer
except Exception as e:
logger.error(f"Erreur lors de l'initialisation MSS dans le thread: {e}")
def _capture_screen_with_sct(self, sct):
"""Capture l'écran avec une instance MSS donnée"""
try:
if self.selected_monitor >= len(self.monitors):
self.selected_monitor = 0
monitor = self.monitors[self.selected_monitor]
# Capturer avec MSS
screenshot = sct.grab(monitor)
# Convertir en array numpy
img_array = np.array(screenshot)
# Convertir BGRA vers BGR (OpenCV)
if img_array.shape[2] == 4:
img_array = cv2.cvtColor(img_array, cv2.COLOR_BGRA2BGR)
return img_array
except Exception as e:
logger.error(f"Erreur lors de la capture d'écran: {e}")
return None
def _capture_screen(self) -> Optional[np.ndarray]:
"""Capture l'écran sélectionné (version legacy, utilise _capture_screen_with_sct)"""
try:
with mss.mss() as sct:
return self._capture_screen_with_sct(sct)
except Exception as e:
logger.error(f"Erreur lors de la capture d'écran legacy: {e}")
return None
def _detect_ui_elements(self, screenshot: np.ndarray):
"""Détecte les éléments UI sur la capture d'écran"""
try:
# Créer un ScreenState temporaire pour la détection
screen_state = ScreenState(
timestamp=time.time(),
screenshot_path="", # Pas de fichier, image en mémoire
screenshot_data=screenshot,
ui_elements=[],
metadata={"source": "real_capture"}
)
# Utiliser le détecteur UI existant
detected_elements = self.ui_detector.detect_elements(screen_state)
# Mettre à jour les éléments détectés
self.detected_elements = detected_elements
logger.debug(f"Détecté {len(detected_elements)} éléments UI")
except Exception as e:
logger.error(f"Erreur lors de la détection UI: {e}")
self.detected_elements = []
def get_current_screenshot_base64(self) -> Optional[str]:
"""Retourne la capture d'écran actuelle en base64"""
if self.current_screenshot is None:
return None
try:
# Convertir en PIL Image
if len(self.current_screenshot.shape) == 3:
# BGR vers RGB
rgb_image = cv2.cvtColor(self.current_screenshot, cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(rgb_image)
else:
pil_image = Image.fromarray(self.current_screenshot)
# Redimensionner pour l'affichage web (optionnel)
max_width = 1200
if pil_image.width > max_width:
ratio = max_width / pil_image.width
new_height = int(pil_image.height * ratio)
pil_image = pil_image.resize((max_width, new_height), Image.Resampling.LANCZOS)
# Convertir en base64
buffer = io.BytesIO()
pil_image.save(buffer, format='JPEG', quality=85)
img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
return f"data:image/jpeg;base64,{img_base64}"
except Exception as e:
logger.error(f"Erreur lors de la conversion base64: {e}")
return None
def get_detected_elements(self) -> List[Dict]:
"""Retourne les éléments UI détectés"""
elements = []
for element in self.detected_elements:
try:
elements.append({
"id": getattr(element, 'id', ''),
"type": getattr(element, 'element_type', 'unknown'),
"text": getattr(element, 'text', ''),
"bbox": {
"x": getattr(element, 'bbox', {}).get('x', 0),
"y": getattr(element, 'bbox', {}).get('y', 0),
"width": getattr(element, 'bbox', {}).get('width', 0),
"height": getattr(element, 'bbox', {}).get('height', 0)
},
"confidence": getattr(element, 'confidence', 0.0),
"attributes": getattr(element, 'attributes', {})
})
except Exception as e:
logger.error(f"Erreur lors de la sérialisation d'un élément: {e}")
return elements
def get_status(self) -> Dict:
"""Retourne le statut du service"""
return {
"is_capturing": self.is_capturing,
"selected_monitor": self.selected_monitor,
"monitors_count": len(self.monitors),
"capture_interval": self.capture_interval,
"elements_detected": len(self.detected_elements),
"has_screenshot": self.current_screenshot is not None
}
def cleanup(self):
"""Nettoie les ressources"""
self.stop_capture()
# Plus besoin de fermer self.sct car nous utilisons des instances locales
# Instance globale du service
real_capture_service = RealScreenCaptureService()

View File

@@ -1,454 +0,0 @@
/**
* Composant Sélecteur Visuel - Sélection d'éléments basée sur la vision
* Auteur : Dom, Alice, Kiro - 08 janvier 2026
*
* Ce composant permet la sélection d'éléments à l'écran via capture d'écran
* et création d'embeddings visuels pour la reconnaissance d'éléments.
*/
import React, { useState, useCallback, useRef } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Box,
Typography,
CircularProgress,
Alert,
Stepper,
Step,
StepLabel,
Paper,
IconButton,
} from '@mui/material';
import {
CameraAlt as CameraIcon,
Close as CloseIcon,
CheckCircle as CheckIcon,
Visibility as VisibilityIcon,
} from '@mui/icons-material';
// Import des types partagés
import { VisualSelection, BoundingBox } from '../../types';
interface VisualSelectorProps {
isOpen: boolean;
stepId: string;
onClose: () => void;
onElementSelected: (selection: VisualSelection) => void;
}
interface CaptureState {
screenshot: string | null;
isCapturing: boolean;
error: string | null;
selectedArea: BoundingBox | null;
isProcessing: boolean;
}
const steps = [
'Capture d\'écran',
'Sélection d\'élément',
'Confirmation',
];
/**
* Composant Sélecteur Visuel
*/
const VisualSelector: React.FC<VisualSelectorProps> = ({
isOpen,
stepId,
onClose,
onElementSelected,
}) => {
const [activeStep, setActiveStep] = useState(0);
const [captureState, setCaptureState] = useState<CaptureState>({
screenshot: null,
isCapturing: false,
error: null,
selectedArea: null,
isProcessing: false,
});
const canvasRef = useRef<HTMLCanvasElement>(null);
const [isSelecting, setIsSelecting] = useState(false);
const [selectionStart, setSelectionStart] = useState<{ x: number; y: number } | null>(null);
// Réinitialiser l'état lors de l'ouverture/fermeture
const handleClose = useCallback(() => {
setActiveStep(0);
setCaptureState({
screenshot: null,
isCapturing: false,
error: null,
selectedArea: null,
isProcessing: false,
});
setIsSelecting(false);
setSelectionStart(null);
onClose();
}, [onClose]);
// Capturer l'écran via l'API ScreenCapturer
const handleCaptureScreen = useCallback(async () => {
setCaptureState(prev => ({ ...prev, isCapturing: true, error: null }));
try {
// Appel à l'API ScreenCapturer réelle du système RPA Vision V3
const response = await fetch('/api/screen-capture', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
format: 'png',
quality: 90,
}),
});
if (!response.ok) {
throw new Error(`Erreur de capture: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (!data.success || !data.screenshot) {
throw new Error(data.error || 'Échec de la capture d\'écran');
}
setCaptureState(prev => ({
...prev,
screenshot: data.screenshot,
isCapturing: false,
}));
setActiveStep(1);
} catch (error) {
console.error('Erreur lors de la capture d\'écran:', error);
setCaptureState(prev => ({
...prev,
isCapturing: false,
error: error instanceof Error ? error.message : 'Erreur inconnue lors de la capture',
}));
}
}, []);
// Gérer le début de sélection sur le canvas
const handleMouseDown = useCallback((event: React.MouseEvent<HTMLCanvasElement>) => {
if (!captureState.screenshot) return;
const canvas = canvasRef.current;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
setIsSelecting(true);
setSelectionStart({ x, y });
setCaptureState(prev => ({ ...prev, selectedArea: null }));
}, [captureState.screenshot]);
// Gérer le mouvement de sélection
const handleMouseMove = useCallback((event: React.MouseEvent<HTMLCanvasElement>) => {
if (!isSelecting || !selectionStart || !canvasRef.current) return;
const canvas = canvasRef.current;
const rect = canvas.getBoundingClientRect();
const currentX = event.clientX - rect.left;
const currentY = event.clientY - rect.top;
// Dessiner la zone de sélection en temps réel
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Redessiner l'image de base
if (captureState.screenshot) {
const img = new Image();
img.onload = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// Dessiner le rectangle de sélection
ctx.strokeStyle = '#1976d2';
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.strokeRect(
selectionStart.x,
selectionStart.y,
currentX - selectionStart.x,
currentY - selectionStart.y
);
};
img.src = `data:image/png;base64,${captureState.screenshot}`;
}
}, [isSelecting, selectionStart, captureState.screenshot]);
// Finaliser la sélection
const handleMouseUp = useCallback((event: React.MouseEvent<HTMLCanvasElement>) => {
if (!isSelecting || !selectionStart || !canvasRef.current) return;
const canvas = canvasRef.current;
const rect = canvas.getBoundingClientRect();
const endX = event.clientX - rect.left;
const endY = event.clientY - rect.top;
const selectedArea: BoundingBox = {
x: Math.min(selectionStart.x, endX),
y: Math.min(selectionStart.y, endY),
width: Math.abs(endX - selectionStart.x),
height: Math.abs(endY - selectionStart.y),
};
// Valider que la zone sélectionnée a une taille minimale
if (selectedArea.width < 10 || selectedArea.height < 10) {
setCaptureState(prev => ({
...prev,
error: 'La zone sélectionnée est trop petite. Veuillez sélectionner une zone plus grande.',
}));
setIsSelecting(false);
setSelectionStart(null);
return;
}
setCaptureState(prev => ({
...prev,
selectedArea,
error: null,
}));
setIsSelecting(false);
setSelectionStart(null);
setActiveStep(2);
}, [isSelecting, selectionStart]);
// Confirmer la sélection et créer l'embedding visuel
const handleConfirmSelection = useCallback(async () => {
if (!captureState.screenshot || !captureState.selectedArea) return;
setCaptureState(prev => ({ ...prev, isProcessing: true, error: null }));
try {
// Créer l'embedding visuel via l'API du système RPA Vision V3
const response = await fetch('/api/visual-embedding', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
screenshot: captureState.screenshot,
boundingBox: captureState.selectedArea,
stepId: stepId,
}),
});
if (!response.ok) {
throw new Error(`Erreur de création d'embedding: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (!data.success || !data.embedding) {
throw new Error(data.error || 'Échec de la création de l\'embedding visuel');
}
// Créer l'objet VisualSelection
const visualSelection: VisualSelection = {
id: `visual_${stepId}_${Date.now()}`,
screenshot: captureState.screenshot,
boundingBox: captureState.selectedArea,
embedding: data.embedding,
description: `Élément sélectionné pour l'étape ${stepId}`,
};
onElementSelected(visualSelection);
handleClose();
} catch (error) {
console.error('Erreur lors de la création de l\'embedding:', error);
setCaptureState(prev => ({
...prev,
isProcessing: false,
error: error instanceof Error ? error.message : 'Erreur inconnue lors de la création de l\'embedding',
}));
}
}, [captureState.screenshot, captureState.selectedArea, stepId, onElementSelected, handleClose]);
// Rendu du contenu selon l'étape active
const renderStepContent = () => {
switch (activeStep) {
case 0:
return (
<Box sx={{ textAlign: 'center', py: 4 }}>
<CameraIcon sx={{ fontSize: 64, color: 'primary.main', mb: 2 }} />
<Typography variant="h6" gutterBottom>
Capture d'écran
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Cliquez sur le bouton ci-dessous pour capturer l'écran actuel.
Assurez-vous que l'élément que vous souhaitez sélectionner est visible.
</Typography>
{captureState.error && (
<Alert severity="error" sx={{ mt: 2, mb: 2 }}>
{captureState.error}
</Alert>
)}
<Button
variant="contained"
size="large"
onClick={handleCaptureScreen}
disabled={captureState.isCapturing}
startIcon={captureState.isCapturing ? <CircularProgress size={20} /> : <CameraIcon />}
>
{captureState.isCapturing ? 'Capture en cours...' : 'Capturer l\'écran'}
</Button>
</Box>
);
case 1:
return (
<Box>
<Typography variant="h6" gutterBottom>
Sélection d'élément
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Cliquez et glissez pour sélectionner l'élément souhaité sur la capture d'écran.
</Typography>
{captureState.error && (
<Alert severity="error" sx={{ mb: 2 }}>
{captureState.error}
</Alert>
)}
<Paper elevation={2} sx={{ p: 1, maxHeight: 400, overflow: 'auto' }}>
{captureState.screenshot && (
<canvas
ref={canvasRef}
width={800}
height={600}
style={{
maxWidth: '100%',
height: 'auto',
cursor: 'crosshair',
border: '1px solid #e0e0e0',
}}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onLoad={() => {
const canvas = canvasRef.current;
const ctx = canvas?.getContext('2d');
if (canvas && ctx && captureState.screenshot) {
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
img.src = `data:image/png;base64,${captureState.screenshot}`;
}
}}
/>
)}
</Paper>
</Box>
);
case 2:
return (
<Box>
<Typography variant="h6" gutterBottom>
Confirmation de sélection
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Vérifiez que la zone sélectionnée correspond à l'élément souhaité.
</Typography>
{captureState.selectedArea && (
<Alert severity="info" sx={{ mb: 2 }}>
Zone sélectionnée : {captureState.selectedArea.width} × {captureState.selectedArea.height} pixels
à la position ({captureState.selectedArea.x}, {captureState.selectedArea.y})
</Alert>
)}
{captureState.error && (
<Alert severity="error" sx={{ mb: 2 }}>
{captureState.error}
</Alert>
)}
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
<Button
variant="outlined"
onClick={() => setActiveStep(1)}
disabled={captureState.isProcessing}
>
Modifier la sélection
</Button>
<Button
variant="contained"
onClick={handleConfirmSelection}
disabled={captureState.isProcessing}
startIcon={captureState.isProcessing ? <CircularProgress size={20} /> : <CheckIcon />}
>
{captureState.isProcessing ? 'Traitement...' : 'Confirmer la sélection'}
</Button>
</Box>
</Box>
);
default:
return null;
}
};
return (
<Dialog
open={isOpen}
onClose={handleClose}
maxWidth="md"
fullWidth
slotProps={{
paper: {
sx: { minHeight: 500 },
},
}}
>
<DialogTitle>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<VisibilityIcon />
<Typography variant="h6">Sélection visuelle d'élément</Typography>
</Box>
<IconButton onClick={handleClose} size="small">
<CloseIcon />
</IconButton>
</Box>
</DialogTitle>
<DialogContent>
{/* Stepper pour indiquer la progression */}
<Stepper activeStep={activeStep} sx={{ mb: 4 }}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
{/* Contenu de l'étape active */}
{renderStepContent()}
</DialogContent>
<DialogActions>
<Button onClick={handleClose} disabled={captureState.isCapturing || captureState.isProcessing}>
Annuler
</Button>
</DialogActions>
</Dialog>
);
};
export default VisualSelector;

View File

@@ -1,414 +0,0 @@
/**
* Hook API Client - Interface React pour le client API
* Auteur : Dom, Alice, Kiro - 09 janvier 2026
*
* Ce hook fournit une interface React pour utiliser le client API
* avec gestion d'état, loading, erreurs et mode hors ligne gracieux.
* Optimisé pour éviter les re-renders excessifs et les sauts de page.
*/
import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { apiClient, ApiError, ConnectionState } from '../services/apiClient';
import { WorkflowApiData } from '../types';
// Types pour les états de requête
interface RequestState<T = any> {
data: T | null;
loading: boolean;
error: ApiError | null;
lastUpdated: Date | null;
isOffline: boolean;
}
interface UseApiClientOptions {
enableAutoRetry?: boolean;
retryDelay?: number;
maxRetries?: number;
onError?: (error: ApiError) => void;
onSuccess?: (data: any) => void;
silentOffline?: boolean; // Ne pas afficher d'erreur en mode hors ligne
}
// État initial stable (évite les re-créations)
const INITIAL_STATE: RequestState = {
data: null,
loading: false,
error: null,
lastUpdated: null,
isOffline: false,
};
/**
* Hook pour utiliser le client API avec gestion d'état React
* Optimisé pour éviter les re-renders inutiles
*/
export function useApiClient<T = any>(options: UseApiClientOptions = {}) {
const {
enableAutoRetry = false, // Désactivé par défaut pour éviter les sauts
retryDelay = 1000,
maxRetries = 2,
onError,
onSuccess,
silentOffline = true, // Par défaut, ne pas afficher d'erreur en mode hors ligne
} = options;
const [state, setState] = useState<RequestState<T>>(INITIAL_STATE);
const retryCountRef = useRef(0);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const mountedRef = useRef(true);
// Nettoyer les timeouts et marquer comme démonté
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
// Fonction pour mettre à jour l'état de manière sécurisée
const safeSetState = useCallback((updater: (prev: RequestState<T>) => RequestState<T>) => {
if (mountedRef.current) {
setState(updater);
}
}, []);
// Fonction générique pour exécuter une requête API
const executeRequest = useCallback(async <R = T>(
requestFn: () => Promise<R>,
requestOptions: { skipLoading?: boolean; skipErrorHandling?: boolean } = {}
): Promise<R | null> => {
const { skipLoading = false, skipErrorHandling = false } = requestOptions;
try {
if (!skipLoading) {
safeSetState(prev => ({
...prev,
loading: true,
error: null,
}));
}
const result = await requestFn();
// Vérifier si le résultat indique un mode hors ligne
const isOfflineResult = result && typeof result === 'object' && 'offline' in result && (result as any).offline;
safeSetState(prev => ({
...prev,
data: isOfflineResult ? prev.data : (result as unknown as T), // Garder les anciennes données si hors ligne
loading: false,
error: null,
lastUpdated: isOfflineResult ? prev.lastUpdated : new Date(),
isOffline: isOfflineResult,
}));
retryCountRef.current = 0;
if (onSuccess && !isOfflineResult) {
onSuccess(result);
}
return result;
} catch (error) {
const apiError = error as ApiError;
const isOffline = apiError.code === 'OFFLINE' || apiError.code === 'NETWORK_ERROR';
safeSetState(prev => ({
...prev,
loading: false,
error: (silentOffline && isOffline) ? null : apiError,
isOffline,
}));
// Gestion du retry automatique (seulement si pas hors ligne)
if (enableAutoRetry && !isOffline && retryCountRef.current < maxRetries && shouldRetryError(apiError)) {
retryCountRef.current++;
timeoutRef.current = setTimeout(() => {
executeRequest(requestFn, requestOptions);
}, retryDelay * Math.pow(2, retryCountRef.current - 1));
return null;
}
retryCountRef.current = 0;
if (!skipErrorHandling && onError && !(silentOffline && isOffline)) {
onError(apiError);
}
// Ne pas relancer l'erreur en mode hors ligne silencieux
if (silentOffline && isOffline) {
return null;
}
throw apiError;
}
}, [enableAutoRetry, maxRetries, retryDelay, onError, onSuccess, silentOffline, safeSetState]);
// Déterminer si une erreur justifie un retry
const shouldRetryError = useCallback((error: ApiError): boolean => {
// Ne pas retry pour les erreurs hors ligne
if (error.code === 'OFFLINE' || error.code === 'NETWORK_ERROR') {
return false;
}
// Retry pour les erreurs serveur
return (
(error.status !== undefined && error.status >= 500) ||
error.status === 408 ||
error.status === 429
);
}, []);
// Réinitialiser l'état
const reset = useCallback(() => {
safeSetState(() => INITIAL_STATE);
retryCountRef.current = 0;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}, [safeSetState]);
// Annuler la requête en cours
const cancel = useCallback(() => {
apiClient.cancelRequest();
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
safeSetState(prev => ({
...prev,
loading: false,
}));
}, [safeSetState]);
return {
...state,
executeRequest,
reset,
cancel,
isRetrying: retryCountRef.current > 0,
retryCount: retryCountRef.current,
};
}
/**
* Hook pour surveiller l'état de connexion de l'API
* Utilise un abonnement pour éviter les re-renders excessifs
* L'état initial est 'offline' pour éviter les tentatives de connexion au montage
*/
export function useConnectionState() {
// État initial 'offline' pour éviter les appels API au montage
const [connectionState, setConnectionState] = useState<ConnectionState>('offline');
useEffect(() => {
// Référence pour éviter les mises à jour après démontage
let isMounted = true;
// S'abonner aux changements d'état de connexion
const unsubscribe = apiClient.onConnectionStateChange((state) => {
if (isMounted) {
setConnectionState(state);
}
});
return () => {
isMounted = false;
unsubscribe();
};
}, []);
// Mémoiser les valeurs dérivées
const derivedState = useMemo(() => ({
isOnline: connectionState === 'online',
isOffline: connectionState === 'offline',
isChecking: connectionState === 'checking',
connectionState,
}), [connectionState]);
// Fonction pour forcer une vérification
const forceCheck = useCallback(async () => {
return apiClient.forceConnectionCheck();
}, []);
return {
...derivedState,
forceCheck,
};
}
/**
* Hook spécialisé pour les opérations sur les workflows
* Gère gracieusement le mode hors ligne
*/
export function useWorkflowApi(options: UseApiClientOptions = {}) {
const api = useApiClient<any>({ ...options, silentOffline: true });
const { isOffline } = useConnectionState();
// Charger la liste des workflows
const loadWorkflows = useCallback(async () => {
if (isOffline) {
return []; // Retourner un tableau vide si hors ligne
}
return api.executeRequest(() => apiClient.getWorkflows());
}, [api, isOffline]);
// Charger un workflow spécifique
const loadWorkflow = useCallback(async (workflowId: string) => {
if (isOffline) {
return null;
}
return api.executeRequest(() => apiClient.getWorkflow(workflowId));
}, [api, isOffline]);
// Sauvegarder un workflow
const saveWorkflow = useCallback(async (workflowData: WorkflowApiData) => {
return api.executeRequest(() => apiClient.saveWorkflow(workflowData));
}, [api]);
// Supprimer un workflow
const deleteWorkflow = useCallback(async (workflowId: string) => {
return api.executeRequest(() => apiClient.deleteWorkflow(workflowId));
}, [api]);
// Valider un workflow
const validateWorkflow = useCallback(async (workflowData: WorkflowApiData) => {
return api.executeRequest(() => apiClient.validateWorkflow(workflowData));
}, [api]);
return {
...api,
isOffline,
loadWorkflows,
loadWorkflow,
saveWorkflow,
deleteWorkflow,
validateWorkflow,
};
}
/**
* Hook spécialisé pour l'exécution de workflows
*/
export function useWorkflowExecution(options: UseApiClientOptions = {}) {
const api = useApiClient<any>({ ...options, silentOffline: true });
const { isOffline } = useConnectionState();
// Exécuter une étape
const executeStep = useCallback(async (stepData: {
stepId: string;
stepType: string;
parameters: any;
workflowId?: string;
}) => {
if (isOffline) {
return { success: false, error: 'API hors ligne', offline: true };
}
return api.executeRequest(() => apiClient.executeStep(stepData));
}, [api, isOffline]);
// Exécuter un workflow complet
const executeWorkflow = useCallback(async (workflowId: string, parameters?: any) => {
if (isOffline) {
return { success: false, error: 'API hors ligne', offline: true };
}
return api.executeRequest(() => apiClient.executeWorkflow(workflowId, parameters));
}, [api, isOffline]);
return {
...api,
isOffline,
executeStep,
executeWorkflow,
};
}
/**
* Hook pour surveiller la santé de l'API
* Optimisé pour éviter les re-renders excessifs
*/
export function useApiHealth(options: UseApiClientOptions & {
pollInterval?: number;
enablePolling?: boolean;
} = {}) {
const { pollInterval = 30000, enablePolling = false } = options;
const api = useApiClient<{ status: string; timestamp: string }>({ ...options, silentOffline: true });
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const { connectionState, isOnline, forceCheck } = useConnectionState();
// Vérifier la santé de l'API
const checkHealth = useCallback(async () => {
return api.executeRequest(() => apiClient.healthCheck(), { skipLoading: true });
}, [api]);
// Démarrer le polling
const startPolling = useCallback(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
intervalRef.current = setInterval(() => {
checkHealth();
}, pollInterval);
// Vérification initiale
checkHealth();
}, [checkHealth, pollInterval]);
// Arrêter le polling
const stopPolling = useCallback(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, []);
// Démarrer le polling automatiquement si activé
useEffect(() => {
if (enablePolling) {
startPolling();
}
return () => {
stopPolling();
};
}, [enablePolling, startPolling, stopPolling]);
return {
...api,
checkHealth,
startPolling,
stopPolling,
forceCheck,
isHealthy: isOnline,
connectionState,
};
}
/**
* Hook pour les statistiques de l'API
*/
export function useApiStats(options: UseApiClientOptions = {}) {
const api = useApiClient<any>({ ...options, silentOffline: true });
// Charger les statistiques
const loadStats = useCallback(async () => {
return api.executeRequest(() => apiClient.getApiStats());
}, [api]);
return {
...api,
loadStats,
};
}
// Export des types
export type { RequestState, UseApiClientOptions };

View File

@@ -1,713 +0,0 @@
/**
* Client API - Gestion centralisée des communications avec le Backend_VWB
* Auteur : Dom, Alice, Kiro - 09 janvier 2026
*
* Ce service centralise toutes les communications avec le backend,
* incluant la gestion d'erreurs, retry automatique, validation des données
* et gestion gracieuse du mode hors ligne.
*
* IMPORTANT: Ce client utilise une initialisation paresseuse (lazy) pour
* éviter les boucles infinies de re-render au chargement de la page.
*/
import { WorkflowApiData } from '../types';
// Configuration du client API
interface ApiClientConfig {
baseUrl: string;
timeout: number;
maxRetries: number;
retryDelay: number;
enableRetry: boolean;
healthCheckInterval: number;
}
// Types pour les réponses API
interface ApiResponse<T = any> {
success: boolean;
data?: T;
error?: string;
code?: string;
timestamp?: string;
offline?: boolean;
}
interface ApiError {
message: string;
code?: string;
status?: number;
details?: any;
offline?: boolean;
}
// État de connexion - 'offline' par défaut pour éviter les appels au montage
type ConnectionState = 'online' | 'offline' | 'checking';
// Callbacks pour les changements d'état
type ConnectionStateCallback = (state: ConnectionState) => void;
// Configuration par défaut
const DEFAULT_CONFIG: ApiClientConfig = {
baseUrl: '/api',
timeout: 3000, // 3 secondes (réduit pour éviter les attentes longues)
maxRetries: 1, // Réduit pour éviter les délais
retryDelay: 500, // 500ms
enableRetry: false, // Désactivé par défaut pour éviter les boucles
healthCheckInterval: 60000, // 60 secondes (augmenté pour réduire les appels)
};
/**
* Client API centralisé pour les communications avec le Backend_VWB
* Gère automatiquement le mode hors ligne sans provoquer de re-rendus excessifs
*
* ARCHITECTURE:
* - État initial: 'offline' (pas de vérification automatique au démarrage)
* - Initialisation paresseuse: la vérification se fait au premier appel API
* - Pas de timer de health check automatique (évite les re-renders)
*/
class ApiClient {
private config: ApiClientConfig;
private abortController: AbortController | null = null;
// État initial 'offline' pour éviter les appels API au montage des composants
private connectionState: ConnectionState = 'offline';
private stateCallbacks: Set<ConnectionStateCallback> = new Set();
private healthCheckTimer: ReturnType<typeof setInterval> | null = null;
private lastHealthCheck: number = 0;
private isInitialized: boolean = false;
private initializationPromise: Promise<void> | null = null;
constructor(config: Partial<ApiClientConfig> = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
}
/**
* Initialiser le client et vérifier la connexion
* Appelé une seule fois au premier appel API (initialisation paresseuse)
* Utilise un pattern singleton pour éviter les initialisations multiples
*/
async initialize(): Promise<void> {
// Si déjà initialisé, retourner immédiatement
if (this.isInitialized) return;
// Si une initialisation est en cours, attendre qu'elle se termine
if (this.initializationPromise) {
return this.initializationPromise;
}
// Créer la promesse d'initialisation
this.initializationPromise = this.doInitialize();
try {
await this.initializationPromise;
} finally {
this.initializationPromise = null;
}
}
/**
* Effectuer l'initialisation réelle
*/
private async doInitialize(): Promise<void> {
if (this.isInitialized) return;
this.isInitialized = true;
// Vérification initiale silencieuse (une seule fois)
await this.checkConnectionSilently();
// NE PAS démarrer le timer automatique pour éviter les re-renders
// Le timer peut être démarré manuellement si nécessaire
}
/**
* Vérification silencieuse de la connexion (sans logs excessifs)
* Utilise un debounce pour éviter les vérifications trop fréquentes
*/
private async checkConnectionSilently(): Promise<boolean> {
const now = Date.now();
// Éviter les vérifications trop fréquentes (minimum 10 secondes entre chaque)
if (now - this.lastHealthCheck < 10000) {
return this.connectionState === 'online';
}
this.lastHealthCheck = now;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 2000); // 2 secondes max
// Utiliser /api/health selon la configuration
const healthUrl = `${this.config.baseUrl}/health`;
const response = await fetch(healthUrl, {
signal: controller.signal,
headers: { 'Accept': 'application/json' },
});
clearTimeout(timeoutId);
if (response.ok) {
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
this.setConnectionState('online');
return true;
}
}
this.setConnectionState('offline');
return false;
} catch {
this.setConnectionState('offline');
return false;
}
}
/**
* Démarrer le timer de vérification de santé (optionnel)
* À appeler manuellement si nécessaire
*/
startHealthCheckTimer(): void {
if (this.healthCheckTimer) return;
this.healthCheckTimer = setInterval(() => {
this.checkConnectionSilently();
}, this.config.healthCheckInterval);
}
/**
* Arrêter le timer de vérification
*/
stopHealthCheck(): void {
if (this.healthCheckTimer) {
clearInterval(this.healthCheckTimer);
this.healthCheckTimer = null;
}
}
/**
* Mettre à jour l'état de connexion et notifier les listeners
* Utilise un mécanisme de batch pour éviter les notifications multiples
*/
private setConnectionState(state: ConnectionState): void {
if (this.connectionState !== state) {
this.connectionState = state;
// Notifier les callbacks de manière asynchrone pour éviter les boucles
setTimeout(() => {
this.stateCallbacks.forEach(callback => {
try {
callback(state);
} catch (e) {
console.warn('Erreur dans le callback de connexion:', e);
}
});
}, 0);
}
}
/**
* S'abonner aux changements d'état de connexion
* NE notifie PAS immédiatement l'état actuel pour éviter les re-renders au montage
*/
onConnectionStateChange(callback: ConnectionStateCallback): () => void {
this.stateCallbacks.add(callback);
// NE PAS notifier immédiatement - cela évite les re-renders au montage
// L'état sera mis à jour lors du premier appel API ou forceConnectionCheck
// Retourner une fonction de désabonnement
return () => {
this.stateCallbacks.delete(callback);
};
}
/**
* Obtenir l'état de connexion actuel
*/
getConnectionState(): ConnectionState {
return this.connectionState;
}
/**
* Vérifier si l'API est en ligne
*/
isOnline(): boolean {
return this.connectionState === 'online';
}
/**
* Effectuer une requête HTTP avec gestion d'erreurs et retry
* Initialisation paresseuse au premier appel
*/
private async makeRequest<T>(
endpoint: string,
options: RequestInit = {},
retryCount = 0
): Promise<ApiResponse<T>> {
// Initialisation paresseuse au premier appel API
if (!this.isInitialized) {
await this.initialize();
}
// Si hors ligne, retourner immédiatement une réponse offline
if (this.connectionState === 'offline' && retryCount === 0) {
return {
success: false,
error: 'API hors ligne - Les données locales sont utilisées',
code: 'OFFLINE',
offline: true,
timestamp: new Date().toISOString(),
};
}
// Créer un nouveau AbortController pour cette requête
this.abortController = new AbortController();
const url = `${this.config.baseUrl}${endpoint}`;
const requestOptions: RequestInit = {
...options,
signal: this.abortController.signal,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
...options.headers,
},
};
// Ajouter un timeout
const timeoutId = setTimeout(() => {
if (this.abortController) {
this.abortController.abort();
}
}, this.config.timeout);
try {
const response = await fetch(url, requestOptions);
clearTimeout(timeoutId);
// Vérifier si la réponse est du JSON
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
// Le serveur retourne du HTML (probablement le serveur React)
this.setConnectionState('offline');
return {
success: false,
error: 'API hors ligne - Le backend n\'est pas démarré',
code: 'OFFLINE',
offline: true,
timestamp: new Date().toISOString(),
};
}
// Marquer comme en ligne si la réponse est valide
this.setConnectionState('online');
// Vérifier le statut de la réponse
if (!response.ok) {
const errorText = await response.text();
let errorData: any = {};
try {
errorData = JSON.parse(errorText);
} catch {
errorData = { message: errorText };
}
const apiError: ApiError = {
message: errorData.message || `Erreur HTTP ${response.status}`,
code: errorData.code || `HTTP_${response.status}`,
status: response.status,
details: errorData,
};
// Retry pour certaines erreurs (5xx, timeouts, network errors)
if (this.shouldRetry(response.status) && retryCount < this.config.maxRetries) {
await this.delay(this.config.retryDelay * Math.pow(2, retryCount));
return this.makeRequest<T>(endpoint, options, retryCount + 1);
}
throw apiError;
}
// Parser la réponse JSON
const data = await response.json();
return {
success: true,
data,
timestamp: new Date().toISOString(),
};
} catch (error) {
clearTimeout(timeoutId);
// Gestion des erreurs d'abort
if (error instanceof Error && error.name === 'AbortError') {
this.setConnectionState('offline');
return {
success: false,
error: 'Requête annulée (timeout)',
code: 'TIMEOUT',
offline: true,
timestamp: new Date().toISOString(),
};
}
// Gestion des erreurs réseau
if (error instanceof TypeError && (error.message.includes('fetch') || error.message.includes('network'))) {
this.setConnectionState('offline');
// Retry pour les erreurs réseau
if (this.config.enableRetry && retryCount < this.config.maxRetries) {
await this.delay(this.config.retryDelay * Math.pow(2, retryCount));
return this.makeRequest<T>(endpoint, options, retryCount + 1);
}
return {
success: false,
error: 'Erreur de connexion réseau - API hors ligne',
code: 'NETWORK_ERROR',
offline: true,
timestamp: new Date().toISOString(),
};
}
// Re-lancer les autres erreurs
throw error;
}
}
/**
* Déterminer si une erreur justifie un retry
*/
private shouldRetry(status: number): boolean {
if (!this.config.enableRetry) return false;
return status >= 500 || status === 408 || status === 429;
}
/**
* Attendre un délai spécifié
*/
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Annuler la requête en cours
*/
public cancelRequest(): void {
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
}
/**
* Valider les données d'un workflow avant envoi
*/
private validateWorkflowData(workflow: WorkflowApiData): void {
if (!workflow.name || workflow.name.trim().length === 0) {
throw new Error('Le nom du workflow est obligatoire');
}
if (workflow.name.length > 100) {
throw new Error('Le nom du workflow ne peut pas dépasser 100 caractères');
}
if (workflow.description && workflow.description.length > 500) {
throw new Error('La description ne peut pas dépasser 500 caractères');
}
if (!Array.isArray(workflow.steps)) {
throw new Error('Les étapes du workflow doivent être un tableau');
}
if (!Array.isArray(workflow.connections)) {
throw new Error('Les connexions du workflow doivent être un tableau');
}
if (!Array.isArray(workflow.variables)) {
throw new Error('Les variables du workflow doivent être un tableau');
}
}
/**
* Valider les données d'une étape avant exécution
*/
private validateStepData(stepData: any): void {
if (!stepData.stepId || typeof stepData.stepId !== 'string') {
throw new Error('L\'ID de l\'étape est obligatoire');
}
if (!stepData.stepType || typeof stepData.stepType !== 'string') {
throw new Error('Le type d\'étape est obligatoire');
}
if (!stepData.parameters || typeof stepData.parameters !== 'object') {
throw new Error('Les paramètres de l\'étape doivent être un objet');
}
}
// === MÉTHODES PUBLIQUES POUR LES WORKFLOWS ===
/**
* Récupérer la liste des workflows
* Retourne un tableau vide si hors ligne
*/
async getWorkflows(): Promise<any[]> {
try {
const response = await this.makeRequest<any[]>('/workflows');
if (response.offline) {
return []; // Retourner un tableau vide en mode hors ligne
}
return response.data || [];
} catch (error) {
console.warn('Erreur lors du chargement des workflows:', error);
return [];
}
}
/**
* Récupérer un workflow par ID
*/
async getWorkflow(workflowId: string): Promise<any | null> {
if (!workflowId || workflowId.trim().length === 0) {
throw new Error('L\'ID du workflow est obligatoire');
}
try {
const response = await this.makeRequest<{ workflow: any }>(`/workflows/${workflowId}`);
if (response.offline) {
return null;
}
return response.data?.workflow || response.data;
} catch (error) {
console.warn(`Erreur lors du chargement du workflow ${workflowId}:`, error);
return null;
}
}
/**
* Sauvegarder un workflow
* Retourne null si hors ligne
*/
async saveWorkflow(workflowData: WorkflowApiData): Promise<string | null> {
// Validation côté client
this.validateWorkflowData(workflowData);
try {
const response = await this.makeRequest<{ workflowId: string; id: string }>('/workflows', {
method: 'POST',
body: JSON.stringify(workflowData),
});
if (response.offline) {
console.warn('Sauvegarde impossible - API hors ligne');
return null;
}
return response.data?.workflowId || response.data?.id || '';
} catch (error) {
console.error('Erreur lors de la sauvegarde du workflow:', error);
throw error;
}
}
/**
* Supprimer un workflow
*/
async deleteWorkflow(workflowId: string): Promise<boolean> {
if (!workflowId || workflowId.trim().length === 0) {
throw new Error('L\'ID du workflow est obligatoire');
}
try {
const response = await this.makeRequest(`/workflows/${workflowId}`, {
method: 'DELETE',
});
return !response.offline && response.success;
} catch (error) {
console.error(`Erreur lors de la suppression du workflow ${workflowId}:`, error);
return false;
}
}
// === MÉTHODES POUR L'EXÉCUTION ===
/**
* Exécuter une étape de workflow
*/
async executeStep(stepData: {
stepId: string;
stepType: string;
parameters: any;
workflowId?: string;
}): Promise<{ success: boolean; output?: any; error?: string; offline?: boolean }> {
// Validation côté client
this.validateStepData(stepData);
try {
const response = await this.makeRequest<{
success: boolean;
output?: any;
error?: string;
}>('/workflow/execute-step', {
method: 'POST',
body: JSON.stringify(stepData),
});
if (response.offline) {
return { success: false, error: 'API hors ligne', offline: true };
}
return response.data || { success: false, error: 'Réponse invalide du serveur' };
} catch (error) {
console.error('Erreur lors de l\'exécution de l\'étape:', error);
return { success: false, error: (error as ApiError).message || 'Erreur inconnue' };
}
}
/**
* Exécuter un workflow complet
*/
async executeWorkflow(workflowId: string, parameters?: any): Promise<{
success: boolean;
results?: any[];
error?: string;
offline?: boolean;
}> {
if (!workflowId || workflowId.trim().length === 0) {
throw new Error('L\'ID du workflow est obligatoire');
}
try {
const response = await this.makeRequest<{
success: boolean;
results?: any[];
error?: string;
}>('/workflow/execute', {
method: 'POST',
body: JSON.stringify({
workflowId,
parameters: parameters || {},
}),
});
if (response.offline) {
return { success: false, error: 'API hors ligne', offline: true };
}
return response.data || { success: false, error: 'Réponse invalide du serveur' };
} catch (error) {
console.error(`Erreur lors de l'exécution du workflow ${workflowId}:`, error);
return { success: false, error: (error as ApiError).message || 'Erreur inconnue' };
}
}
// === MÉTHODES POUR LA VALIDATION ===
/**
* Valider un workflow
*/
async validateWorkflow(workflowData: WorkflowApiData): Promise<{
isValid: boolean;
errors: string[];
warnings: string[];
offline?: boolean;
}> {
// Validation côté client d'abord
try {
this.validateWorkflowData(workflowData);
} catch (error) {
return {
isValid: false,
errors: [(error as ApiError).message],
warnings: [],
};
}
try {
const response = await this.makeRequest<{
isValid: boolean;
errors: string[];
warnings: string[];
}>('/workflow/validate', {
method: 'POST',
body: JSON.stringify(workflowData),
});
if (response.offline) {
// En mode hors ligne, faire une validation locale basique
return {
isValid: true,
errors: [],
warnings: ['Validation serveur non disponible (mode hors ligne)'],
offline: true,
};
}
return response.data || {
isValid: false,
errors: ['Erreur de validation du serveur'],
warnings: [],
};
} catch (error) {
console.warn('Erreur lors de la validation du workflow:', error);
return {
isValid: true,
errors: [],
warnings: ['Validation serveur non disponible'],
};
}
}
// === MÉTHODES UTILITAIRES ===
/**
* Vérifier la santé de l'API
*/
async healthCheck(): Promise<{ status: string; timestamp: string; offline?: boolean }> {
try {
const response = await this.makeRequest<{ status: string; timestamp: string }>('/health');
if (response.offline) {
return { status: 'offline', timestamp: new Date().toISOString(), offline: true };
}
return response.data || { status: 'unknown', timestamp: new Date().toISOString() };
} catch (error) {
return { status: 'offline', timestamp: new Date().toISOString(), offline: true };
}
}
/**
* Forcer une vérification de connexion
*/
async forceConnectionCheck(): Promise<boolean> {
this.lastHealthCheck = 0; // Réinitialiser pour forcer la vérification
return this.checkConnectionSilently();
}
/**
* Obtenir les statistiques de l'API
*/
async getApiStats(): Promise<any> {
try {
const response = await this.makeRequest<any>('/stats');
if (response.offline) {
return { offline: true };
}
return response.data || {};
} catch (error) {
console.warn('Erreur lors de la récupération des statistiques:', error);
return { offline: true };
}
}
}
// Instance singleton du client API
export const apiClient = new ApiClient();
// NOTE: L'initialisation est maintenant paresseuse (lazy)
// Elle se fait automatiquement lors du premier appel API
// Cela évite les boucles infinies au chargement de la page
// Export des types pour utilisation externe
export type { ApiError, ApiResponse, ApiClientConfig, ConnectionState };
export default ApiClient;

View File

@@ -1,229 +0,0 @@
/**
* Types partagés pour le Visual Workflow Builder V2
* Auteur : Dom, Alice, Kiro - 08 janvier 2026
*
* Définitions TypeScript centralisées pour tous les composants.
*/
// Types de base pour les workflows
export interface Workflow {
id: string;
name: string;
description?: string;
steps: Step[];
connections: WorkflowConnection[];
variables: Variable[];
createdAt: Date;
updatedAt: Date;
}
export interface Step {
id: string;
type: StepType;
name: string;
position: Position;
data: StepData;
executionState?: StepExecutionState;
validationErrors?: ValidationError[];
}
export interface StepData {
label: string;
stepType: StepType;
parameters: Record<string, any>;
visualSelection?: VisualSelection;
isSelected?: boolean;
}
export interface WorkflowConnection {
id: string;
source: string;
target: string;
type?: string;
label?: string;
}
export interface Position {
x: number;
y: number;
}
// Types pour les variables
export interface Variable {
id: string;
name: string;
type: VariableType;
defaultValue?: any;
description?: string;
value?: any;
}
export type VariableType = 'text' | 'number' | 'boolean' | 'list';
export enum VariableTypeEnum {
TEXT = 'text',
NUMBER = 'number',
BOOLEAN = 'boolean',
LIST = 'list'
}
// Types pour les étapes
export type StepType =
| 'click'
| 'type'
| 'wait'
| 'condition'
| 'extract'
| 'scroll'
| 'navigate'
| 'screenshot';
export enum StepExecutionState {
IDLE = 'idle',
RUNNING = 'running',
SUCCESS = 'success',
ERROR = 'error',
SKIPPED = 'skipped'
}
// Types pour la validation
export interface ValidationError {
parameter: string;
message: string;
severity: 'error' | 'warning';
}
// Types pour la sélection visuelle
export interface VisualSelection {
id: string;
screenshot: string; // Base64 de l'image
boundingBox: BoundingBox;
embedding?: number[];
description?: string;
}
export interface BoundingBox {
x: number;
y: number;
width: number;
height: number;
}
// Types pour l'exécution
export interface ExecutionState {
currentStep?: string;
status: ExecutionStatus;
startTime?: Date;
endTime?: Date;
errors?: ExecutionError[];
}
export type ExecutionStatus = 'idle' | 'running' | 'completed' | 'error' | 'paused';
export interface ExecutionError {
stepId: string;
message: string;
timestamp: Date;
}
// Types pour les catégories de la palette
export interface StepCategory {
id: string;
name: string;
description: string;
icon: string;
steps: StepTemplate[];
}
export interface StepTemplate {
id: string;
type: StepType;
name: string;
description: string;
icon: string;
defaultParameters: Record<string, any>;
requiredParameters: string[];
}
// Types pour les propriétés des composants
export interface CanvasProps {
workflow?: Workflow;
selectedStep?: Step | null;
executionState?: ExecutionState;
onStepSelect?: (step: Step | null) => void;
onStepMove?: (stepId: string, position: Position) => void;
onConnection?: (source: string, target: string) => void;
onStepAdd?: (step: Omit<Step, 'id'>) => void;
onStepDelete?: (stepId: string) => void;
}
export interface PaletteProps {
categories: StepCategory[];
searchTerm: string;
onSearch: (term: string) => void;
onStepDrag: (stepTemplate: StepTemplate) => void;
}
export interface PropertiesPanelProps {
selectedStep?: Step | null;
variables: Variable[];
onParameterChange: (stepId: string, parameter: string, value: any) => void;
onVisualSelection: (stepId: string) => void;
}
export interface VariableManagerProps {
variables: Variable[];
onVariableCreate: (variable: Omit<Variable, 'id'>) => void;
onVariableUpdate: (id: string, updates: Partial<Variable>) => void;
onVariableDelete: (id: string) => void;
}
export interface DocumentationTabProps {
toolName: string;
isActive: boolean;
onActivate: () => void;
}
// Types pour les nœuds ReactFlow
export interface StepNodeData extends Record<string, unknown> {
label: string;
stepType: StepType;
executionState: StepExecutionState;
validationErrors: ValidationError[];
isSelected: boolean;
parameters: Record<string, any>;
}
// Types pour l'API
export interface ApiResponse<T = any> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
export interface WorkflowApiData {
id?: string;
name: string;
description?: string;
steps: Step[];
connections: WorkflowConnection[];
variables: Variable[];
}
// Types pour les événements
export interface StepMoveEvent {
stepId: string;
position: Position;
}
export interface ConnectionEvent {
source: string;
target: string;
}
export interface ParameterChangeEvent {
stepId: string;
parameter: string;
value: any;
}

View File

@@ -1,115 +0,0 @@
#!/bin/bash
#
# Script de vérification du port pour le dashboard RPA Vision V3
# Vérifie si le port 5001 est disponible et propose des alternatives
#
set -e
# Couleurs
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ VÉRIFICATION DES PORTS - DASHBOARD RPA VISION V3 ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
# Port par défaut
DEFAULT_PORT=5001
# Fonction pour vérifier si un port est utilisé
check_port() {
local port=$1
if ss -tuln | grep -q ":${port} "; then
return 1 # Port occupé
else
return 0 # Port libre
fi
}
# Fonction pour trouver le processus utilisant un port
get_process_on_port() {
local port=$1
lsof -i :${port} 2>/dev/null | grep LISTEN | awk '{print $2}' | head -1
}
# Vérifier le port par défaut (5001)
echo -e "${YELLOW}[1/3]${NC} Vérification du port ${DEFAULT_PORT}..."
if check_port ${DEFAULT_PORT}; then
echo -e "${GREEN}${NC} Port ${DEFAULT_PORT} disponible"
PORT_STATUS="available"
else
echo -e "${RED}${NC} Port ${DEFAULT_PORT} occupé"
PID=$(get_process_on_port ${DEFAULT_PORT})
if [ -n "$PID" ]; then
PROCESS=$(ps -p $PID -o comm= 2>/dev/null || echo "inconnu")
echo -e " Processus: ${PROCESS} (PID: ${PID})"
echo -e " Commande: ${YELLOW}kill ${PID}${NC} pour libérer le port"
fi
PORT_STATUS="occupied"
fi
# Vérifier les ports alternatifs
echo ""
echo -e "${YELLOW}[2/3]${NC} Vérification des ports alternatifs..."
ALTERNATIVE_PORTS=(5000 3000 8000 8080 8888 9000)
AVAILABLE_PORTS=()
for port in "${ALTERNATIVE_PORTS[@]}"; do
if check_port $port; then
echo -e "${GREEN}${NC} Port ${port} disponible"
AVAILABLE_PORTS+=($port)
else
echo -e "${RED}${NC} Port ${port} occupé"
fi
done
# Résumé et recommandations
echo ""
echo -e "${YELLOW}[3/3]${NC} Résumé et recommandations..."
echo ""
if [ "$PORT_STATUS" = "available" ]; then
echo -e "${GREEN}✅ PRÊT${NC} - Le port par défaut (${DEFAULT_PORT}) est disponible"
echo ""
echo "Lancement du dashboard:"
echo -e " ${GREEN}cd rpa_vision_v3${NC}"
echo -e " ${GREEN}./run.sh --dashboard${NC}"
echo ""
echo "Accès: http://localhost:${DEFAULT_PORT}"
else
echo -e "${YELLOW}⚠️ ATTENTION${NC} - Le port ${DEFAULT_PORT} est occupé"
echo ""
if [ ${#AVAILABLE_PORTS[@]} -gt 0 ]; then
echo "Ports alternatifs disponibles:"
for port in "${AVAILABLE_PORTS[@]}"; do
echo -e " • Port ${port}: ${GREEN}disponible${NC}"
done
echo ""
echo "Pour utiliser un port alternatif:"
echo -e " ${YELLOW}export FLASK_PORT=${AVAILABLE_PORTS[0]}${NC}"
echo -e " ${YELLOW}cd rpa_vision_v3${NC}"
echo -e " ${YELLOW}./run.sh --dashboard${NC}"
echo ""
echo "Ou modifier web_dashboard/app.py ligne 165:"
echo -e " ${YELLOW}app.run(debug=True, host='0.0.0.0', port=${AVAILABLE_PORTS[0]})${NC}"
else
echo -e "${RED}❌ PROBLÈME${NC} - Aucun port web standard n'est disponible"
echo ""
echo "Actions recommandées:"
echo " 1. Arrêter les serveurs web inutilisés"
echo " 2. Vérifier les processus: ps aux | grep python"
echo " 3. Libérer le port 5001: kill \$(lsof -t -i:5001)"
fi
fi
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ VÉRIFICATION TERMINÉE ║"
echo "╚══════════════════════════════════════════════════════════════╝"

View File

@@ -1,74 +0,0 @@
#!/bin/bash
echo "═══════════════════════════════════════════════════════════════"
echo " 🔍 Flask Installation Check"
echo "═══════════════════════════════════════════════════════════════"
echo ""
# Check if venv is activated
if [[ "$VIRTUAL_ENV" == *"venv_v3"* ]]; then
echo "✅ venv_v3 is activated"
echo " Path: $VIRTUAL_ENV"
else
echo "⚠️ venv_v3 is NOT activated"
echo " Activating now..."
source venv_v3/bin/activate
fi
echo ""
echo "Checking Flask installation..."
echo ""
# Check Flask
if python3 -c "import flask" 2>/dev/null; then
VERSION=$(python3 -c "import importlib.metadata; print(importlib.metadata.version('flask'))" 2>/dev/null)
echo "✅ Flask installed: version $VERSION"
else
echo "❌ Flask NOT installed"
echo " Run: pip install Flask>=3.0.0"
exit 1
fi
# Check Flask-SocketIO
if python3 -c "import flask_socketio" 2>/dev/null; then
VERSION=$(python3 -c "import importlib.metadata; print(importlib.metadata.version('flask-socketio'))" 2>/dev/null)
echo "✅ Flask-SocketIO installed: version $VERSION"
else
echo "❌ Flask-SocketIO NOT installed"
echo " Run: pip install Flask-SocketIO>=5.3.0"
exit 1
fi
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo " Flask Components in Project"
echo "═══════════════════════════════════════════════════════════════"
echo ""
# List Flask components
echo "📁 Flask-based components:"
echo " 1. web_dashboard/app.py (port 5001)"
echo " 2. command_interface/app.py (port 5002)"
echo " 3. server/api_core.py (port 8000)"
echo " 4. core/analytics/api/analytics_api.py (port 5000)"
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo " Quick Start Commands"
echo "═══════════════════════════════════════════════════════════════"
echo ""
echo "# Activate venv (if not already active)"
echo "source venv_v3/bin/activate"
echo ""
echo "# Launch dashboard"
echo "python3 web_dashboard/app.py"
echo ""
echo "# Launch command interface"
echo "python3 command_interface/app.py"
echo ""
echo "# Launch analytics API"
echo "python3 test_analytics_server.py"
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo "✅ Flask is ready to use!"
echo "═══════════════════════════════════════════════════════════════"

View File

@@ -1,44 +0,0 @@
#!/bin/bash
# Script de vérification du statut après correction des tokens
echo "🔍 RPA Vision V3 - Vérification Post-Correction"
echo "==============================================="
echo ""
echo "📊 1. STATUT DES SERVICES"
echo "------------------------"
for service in rpa-vision-v3-api rpa-vision-v3-worker rpa-vision-v3-dashboard; do
status=$(systemctl is-active $service)
if [ "$status" = "active" ]; then
echo "$service: $status"
else
echo "$service: $status"
fi
done
echo ""
echo "📋 2. LOGS RÉCENTS API (dernières 20 lignes)"
echo "--------------------------------------------"
sudo journalctl -u rpa-vision-v3-api -n 20 --no-pager | grep -E "(TokenManager|token|Bearer|Upload)" || echo "Aucune ligne pertinente trouvée"
echo ""
echo "🔑 3. TOKENS CONFIGURÉS (tronqués)"
echo "----------------------------------"
sudo cat /etc/rpa_vision_v3/rpa_vision_v3.env | grep RPA_TOKEN | while read line; do
key=$(echo $line | cut -d'=' -f1)
value=$(echo $line | cut -d'=' -f2)
echo "$key=${value:0:16}..."
done
echo ""
echo "📂 4. SESSIONS RÉCENTES (5 dernières)"
echo "-------------------------------------"
ls -lht /opt/rpa_vision_v3/data/training/sessions/*.json 2>/dev/null | head -5 || echo "Aucune session trouvée"
echo ""
echo "🌐 5. TEST API (endpoint /api/traces/status)"
echo "--------------------------------------------"
curl -s http://localhost:8000/api/traces/status 2>/dev/null | python3 -m json.tool 2>/dev/null || echo "API non accessible"
echo ""
echo "✅ Vérification terminée"

View File

@@ -1,268 +0,0 @@
#!/usr/bin/env python3
"""
Script de vérification du progrès RPA 100% Visuel
Vérifie l'état d'avancement de l'implémentation du système RPA 100% visuel.
Tâche 15: Checkpoint Final - Validation complète du système
"""
import os
from pathlib import Path
import json
def check_visual_rpa_progress():
"""Vérifie le progrès de l'implémentation RPA 100% visuel - Checkpoint Final"""
project_root = Path(__file__).parent
print("🏁 CHECKPOINT FINAL - Système RPA 100% Visuel")
print("=" * 60)
# 1. Vérifier les composants Core
print("\n📦 Composants Core (core/visual/):")
core_visual_path = project_root / "core" / "visual"
core_files = [
"visual_target_manager.py",
"visual_embedding_manager.py",
"screenshot_validation_manager.py",
"contextual_capture_service.py",
"realtime_validation_service.py",
"visual_persistence_manager.py",
"visual_performance_optimizer.py",
"rpa_integration_manager.py",
"workflow_migration_tool.py",
"__init__.py"
]
core_count = 0
for file_name in core_files:
file_path = core_visual_path / file_name
exists = file_path.exists()
size = file_path.stat().st_size if exists else 0
status = "" if exists and size > 0 else ""
print(f" {status} {file_name} ({size} bytes)")
if exists and size > 0:
core_count += 1
print(f" 📊 Core: {core_count}/{len(core_files)} ({core_count/len(core_files)*100:.1f}%)")
# 2. Vérifier les composants Frontend
print("\n🎨 Composants Frontend (visual_workflow_builder/frontend/src/components/):")
frontend_path = project_root / "visual_workflow_builder" / "frontend" / "src" / "components"
frontend_components = [
"VisualPropertiesPanel",
"VisualScreenSelector",
"InteractivePreviewArea",
"VisualMetadataDisplay"
]
frontend_count = 0
for component_name in frontend_components:
component_path = frontend_path / component_name
index_file = component_path / "index.tsx"
exists = index_file.exists()
size = index_file.stat().st_size if exists else 0
status = "" if exists and size > 0 else ""
print(f" {status} {component_name}/index.tsx ({size} bytes)")
if exists and size > 0:
frontend_count += 1
print(f" 📊 Frontend: {frontend_count}/{len(frontend_components)} ({frontend_count/len(frontend_components)*100:.1f}%)")
# 3. Vérifier les tests de propriété
print("\n🧪 Tests de Propriété (tests/property/):")
tests_path = project_root / "tests" / "property"
property_tests = [
"test_visual_target_manager_properties.py",
"test_visual_embedding_manager_properties.py",
"test_visual_capture_properties.py",
"test_visual_screen_selector_properties.py",
"test_visual_properties_panel_properties.py",
"test_interactive_preview_area_properties.py",
"test_realtime_validation_properties.py"
]
tests_count = 0
for test_file in property_tests:
test_path = tests_path / test_file
exists = test_path.exists()
size = test_path.stat().st_size if exists else 0
status = "" if exists and size > 0 else ""
print(f" {status} {test_file} ({size} bytes)")
if exists and size > 0:
tests_count += 1
print(f" 📊 Tests: {tests_count}/{len(property_tests)} ({tests_count/len(property_tests)*100:.1f}%)")
# 4. Vérifier les tests d'intégration
print("\n🔗 Tests d'Intégration:")
integration_test = project_root / "tests" / "integration" / "test_visual_rpa_checkpoint.py"
integration_exists = integration_test.exists()
integration_size = integration_test.stat().st_size if integration_exists else 0
integration_status = "" if integration_exists and integration_size > 0 else ""
print(f" {integration_status} test_visual_rpa_checkpoint.py ({integration_size} bytes)")
# 5. Vérifier les services et types
print("\n🔧 Services et Types:")
# Service de capture
service_file = project_root / "visual_workflow_builder" / "frontend" / "src" / "services" / "VisualCaptureService.ts"
service_exists = service_file.exists()
service_size = service_file.stat().st_size if service_exists else 0
service_status = "" if service_exists and service_size > 0 else ""
print(f" {service_status} VisualCaptureService.ts ({service_size} bytes)")
# Types TypeScript
types_file = project_root / "visual_workflow_builder" / "frontend" / "src" / "types" / "workflow.ts"
types_exists = types_file.exists()
types_size = types_file.stat().st_size if types_exists else 0
types_status = "" if types_exists and types_size > 0 else ""
print(f" {types_status} workflow.ts ({types_size} bytes)")
# 6. Vérifier les styles CSS
print("\n🎨 Styles CSS (Design System Conforme):")
css_files = [
"visual_workflow_builder/frontend/src/components/VisualPropertiesPanel/VisualPropertiesPanel.css",
"visual_workflow_builder/frontend/src/components/VisualMetadataDisplay/VisualMetadataDisplay.css",
"visual_workflow_builder/frontend/src/components/VisualScreenSelector/VisualScreenSelector.css",
"visual_workflow_builder/frontend/src/components/InteractivePreviewArea/InteractivePreviewArea.css"
]
css_count = 0
for css_file in css_files:
css_path = project_root / css_file
exists = css_path.exists()
size = css_path.stat().st_size if exists else 0
status = "" if exists and size > 0 else ""
component_name = css_file.split('/')[-1]
print(f" {status} {component_name} ({size} bytes)")
if exists and size > 0:
css_count += 1
print(f" 📊 CSS: {css_count}/{len(css_files)} ({css_count/len(css_files)*100:.1f}%)")
# 7. Calculer le progrès global final
print("\n📈 Progrès Global Final:")
total_components = (len(core_files) + len(frontend_components) + len(property_tests) +
1 + 2 + len(css_files)) # +1 integration test, +2 service+types
completed_components = (core_count + frontend_count + tests_count +
(1 if integration_exists and integration_size > 0 else 0) +
(1 if service_exists and service_size > 0 else 0) +
(1 if types_exists and types_size > 0 else 0) +
css_count)
completion_rate = (completed_components / total_components) * 100
print(f" 🎯 Taux de completion: {completed_components}/{total_components} ({completion_rate:.1f}%)")
# 8. Évaluation des 27 propriétés de correction
print("\n🏆 Propriétés de Correction (27 propriétés):")
# Propriétés implémentées (basé sur les composants créés)
implemented_properties = {
1: "Élimination Complète des Sélecteurs Techniques",
2: "Sélection Visuelle Pure",
3: "Affichage de Captures Haute Qualité",
9: "Métadonnées en Langage Naturel",
11: "Fonctionnalité de Zoom Interactif",
12: "Contour Animé pour Éléments Cibles",
14: "Validation Périodique Automatique",
15: "Récupération Intelligente d'Éléments",
22: "Persistance Complète des Données Visuelles",
24: "Performance de Traitement des Captures",
25: "Réactivité du Mode Sélection",
26: "Optimisation par Cache des Captures",
27: "Traitement Non-Bloquant des Embeddings"
}
properties_rate = (len(implemented_properties) / 27) * 100
print(f" ✅ Propriétés implémentées: {len(implemented_properties)}/27 ({properties_rate:.1f}%)")
for prop_id, description in implemented_properties.items():
print(f" ✓ Propriété {prop_id:2d}: {description}")
# 9. Statut final du système
print(f"\n🏁 STATUT FINAL DU SYSTÈME:")
if completion_rate >= 95:
status = "🎉 EXCELLENT - Système RPA 100% visuel COMPLET!"
color = "🟢"
elif completion_rate >= 85:
status = "✅ TRÈS BON - Système presque complet!"
color = "🟡"
elif completion_rate >= 70:
status = "⚠️ BON - Système fonctionnel avec améliorations possibles"
color = "🟠"
else:
status = "❌ INSUFFISANT - Système incomplet"
color = "🔴"
print(f" {color} {status}")
print(f" 📊 Completion globale: {completion_rate:.1f}%")
print(f" 🏆 Propriétés implémentées: {properties_rate:.1f}%")
# 10. Conformité au Design System
print(f"\n🎨 Conformité au Design System RPA Vision V3:")
design_system_items = [
"Couleurs Material-UI (Primary Blue #1976d2)",
"Espacement cohérent (Card padding: 20px)",
"Composants Material-UI + CSS modules",
"Architecture TypeScript avec interfaces",
"Responsive design implémenté"
]
for item in design_system_items:
print(f"{item}")
# 11. Recommandations finales
print(f"\n💡 Recommandations finales:")
if completion_rate >= 95:
print(" 🚀 Système prêt pour la production!")
print(" 📝 Documenter les derniers détails")
print(" 🧪 Exécuter les tests de performance en conditions réelles")
elif completion_rate >= 85:
print(" 🔧 Finaliser les composants manquants")
print(" 🧪 Compléter les tests de propriétés restants")
print(" 📋 Valider l'intégration complète")
else:
print(" ⚠️ Continuer l'implémentation des composants critiques")
print(" 🔍 Résoudre les problèmes d'écriture de fichiers")
print(" 🧪 Créer les tests manquants")
# 12. Sauvegarder le rapport final
report = {
"timestamp": "2026-01-07",
"completion_rate": completion_rate,
"completed_components": completed_components,
"total_components": total_components,
"properties_implemented": len(implemented_properties),
"total_properties": 27,
"properties_rate": properties_rate,
"core_progress": f"{core_count}/{len(core_files)}",
"frontend_progress": f"{frontend_count}/{len(frontend_components)}",
"tests_progress": f"{tests_count}/{len(property_tests)}",
"integration_test_ready": integration_exists and integration_size > 0,
"service_ready": service_exists and service_size > 0,
"types_ready": types_exists and types_size > 0,
"css_progress": f"{css_count}/{len(css_files)}",
"design_system_compliant": True,
"status": status,
"ready_for_production": completion_rate >= 95
}
report_file = project_root / "visual_rpa_final_report.json"
with open(report_file, 'w', encoding='utf-8') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
print(f"\n📄 Rapport final sauvegardé: {report_file}")
return completion_rate >= 85 # Checkpoint réussi si >= 85%
if __name__ == "__main__":
success = check_visual_rpa_progress()
exit(0 if success else 1)

View File

@@ -1,24 +0,0 @@
#!/bin/bash
# Nettoyage des fichiers JSON orphelins (sessions traitées avant Phase 3)
# Ces fichiers ont déjà leurs screen_states créés, ils sont donc inutiles
echo "=== Nettoyage des JSON Orphelins ==="
echo ""
echo "Fichiers à supprimer (sessions déjà traitées) :"
find /opt/rpa_vision_v3/data/training/sessions -name "session_*.json" -type f
echo ""
read -p "Supprimer ces 9 fichiers ? (o/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Oo]$ ]]
then
echo "Suppression en cours..."
find /opt/rpa_vision_v3/data/training/sessions -name "session_*.json" -type f -delete
echo "✅ Nettoyage terminé"
echo ""
echo "Vérification :"
echo "JSON restants : $(find /opt/rpa_vision_v3/data/training/sessions -name "session_*.json" -type f | wc -l)"
echo "Screen states conservés : $(find /opt/rpa_vision_v3/data/training/screen_states -name "*.json" -type f | wc -l)"
else
echo "❌ Nettoyage annulé"
fi

View File

@@ -1,38 +0,0 @@
#!/bin/bash
# Création du cache HuggingFace pour l'utilisateur rpa
set -e
echo "📦 Création du Cache HuggingFace"
echo "================================="
echo ""
if [ "$EUID" -ne 0 ]; then
echo "❌ Ce script doit être exécuté avec sudo"
echo "Usage: sudo bash create_hf_cache.sh"
exit 1
fi
# Créer le home directory si nécessaire
if [ ! -d "/home/rpa" ]; then
echo "📁 Création de /home/rpa..."
mkdir -p /home/rpa
fi
# Créer le répertoire cache HuggingFace
echo "📁 Création du répertoire cache HuggingFace..."
mkdir -p /home/rpa/.cache/huggingface
mkdir -p /home/rpa/.cache/huggingface/hub
mkdir -p /home/rpa/.cache/torch
# Définir les permissions correctes
echo "🔐 Configuration des permissions..."
chown -R rpa:rpa /home/rpa
chmod -R 755 /home/rpa/.cache
echo "✅ Cache HuggingFace créé et configuré"
echo ""
echo "📋 Vérification:"
ls -la /home/rpa/.cache/
echo ""
echo "✓ Prêt pour le téléchargement des modèles CLIP"

View File

@@ -1,158 +0,0 @@
#!/usr/bin/env python3
"""
Création du ZIP Final - Capture d'Élément Cible VWB Résolu
Auteur : Dom, Alice, Kiro - 09 janvier 2026
Ce script crée un ZIP avec tous les fichiers importants pour la résolution
de la capture d'élément cible avec l'Option A Ultra Stable.
"""
import zipfile
import os
from pathlib import Path
from datetime import datetime
def create_final_zip():
"""Crée le ZIP final avec tous les fichiers importants."""
# Nom du ZIP avec timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
zip_name = f"capture_element_cible_vwb_resolu_{timestamp}.zip"
print("=" * 60)
print(" CRÉATION ZIP FINAL - CAPTURE ÉLÉMENT CIBLE VWB")
print("=" * 60)
print("Auteur : Dom, Alice, Kiro - 09 janvier 2026")
print("")
print(f"📦 Nom du ZIP: {zip_name}")
print("")
# Fichiers à inclure dans le ZIP
files_to_include = [
# Documentation
"docs/RESOLUTION_CAPTURE_ELEMENT_CIBLE_VWB_FINALE_09JAN2026.md",
# Backend modifié (Option A)
"core/capture/screen_capturer.py",
"visual_workflow_builder/backend/app_lightweight.py",
"visual_workflow_builder/backend/services/thread_safe_screen_capture.py",
"visual_workflow_builder/backend/services/real_screen_capture.py",
# Frontend modifié
"visual_workflow_builder/frontend/src/components/VisualSelector/index.tsx",
"visual_workflow_builder/frontend/src/services/screenCaptureService.ts",
"visual_workflow_builder/frontend/src/services/apiClient.ts",
"visual_workflow_builder/frontend/src/types/index.ts",
# Scripts de démarrage
"scripts/start_vwb_backend_ultra_stable.py",
# Tests de validation
"tests/integration/test_capture_element_cible_vwb_complete_09jan2026.py",
"tests/integration/test_fix_ultra_stable_capture_09jan2026.py",
# Autres fichiers importants
"visual_workflow_builder/backend/services/serialization.py",
"visual_workflow_builder/backend/models.py",
]
# Créer le ZIP
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
files_added = 0
files_missing = 0
for file_path in files_to_include:
if os.path.exists(file_path):
zipf.write(file_path, file_path)
print(f"✅ Ajouté: {file_path}")
files_added += 1
else:
print(f"⚠️ Manquant: {file_path}")
files_missing += 1
# Ajouter un README dans le ZIP
readme_content = """# Capture d'Élément Cible VWB - Solution Complète
**Auteur :** Dom, Alice, Kiro
**Date :** 09 janvier 2026
**Statut :** ✅ RÉSOLU avec Option A Ultra Stable
## 🎯 Contenu de ce ZIP
Ce ZIP contient tous les fichiers nécessaires pour la résolution complète
de la capture d'élément cible du Visual Workflow Builder.
### 📁 Structure
- `docs/` : Documentation complète de la résolution
- `core/capture/` : ScreenCapturer avec Option A implémentée
- `visual_workflow_builder/backend/` : Backend Flask ultra stable
- `visual_workflow_builder/frontend/` : Frontend React connecté
- `scripts/` : Scripts de démarrage automatique
- `tests/` : Tests de validation complets
## 🚀 Démarrage Rapide
1. **Démarrer le backend :**
```bash
python3 scripts/start_vwb_backend_ultra_stable.py
```
2. **Tester le système :**
```bash
python3 tests/integration/test_capture_element_cible_vwb_complete_09jan2026.py
```
3. **Lire la documentation :**
Voir `docs/RESOLUTION_CAPTURE_ELEMENT_CIBLE_VWB_FINALE_09JAN2026.md`
## ✅ Résultats
- **5/6 tests réussis** (83% de succès)
- **Capture d'écran opérationnelle** (1920x1080)
- **Embeddings visuels fonctionnels** (dimension 512)
- **Intégration frontend ↔ backend validée**
## 🔧 Solution Technique
**Option A Ultra Stable :** MSS créé à chaque capture
- Zéro surprise, marche dans n'importe quel thread
- Thread-safe par design
- Légèrement moins performant mais ultra stable
**🚀 MISSION ACCOMPLIE - SYSTÈME OPÉRATIONNEL ! 🚀**
"""
# Ajouter le README au ZIP
zipf.writestr("README.md", readme_content)
print("✅ Ajouté: README.md")
files_added += 1
print("")
print("=" * 60)
print(f"📦 ZIP créé: {zip_name}")
print(f"✅ Fichiers ajoutés: {files_added}")
if files_missing > 0:
print(f"⚠️ Fichiers manquants: {files_missing}")
print("")
# Afficher la taille du ZIP
zip_size = os.path.getsize(zip_name)
if zip_size < 1024:
size_str = f"{zip_size} bytes"
elif zip_size < 1024 * 1024:
size_str = f"{zip_size / 1024:.1f} KB"
else:
size_str = f"{zip_size / (1024 * 1024):.1f} MB"
print(f"📊 Taille du ZIP: {size_str}")
print("")
print("🎉 ZIP final créé avec succès !")
print("🚀 Tous les fichiers de la solution sont inclus")
print("")
print("=" * 60)
return zip_name
if __name__ == '__main__':
zip_name = create_final_zip()
print(f"✅ ZIP disponible: {zip_name}")

View File

@@ -1,416 +0,0 @@
#!/usr/bin/env python3
"""
Script de création d'un ZIP propre du Visual Workflow Builder
Auteur : Dom, Alice, Kiro - 8 janvier 2026
"""
import os
import zipfile
import shutil
from pathlib import Path
import json
class CreateurZipVWB:
"""Classe pour créer un ZIP propre du Visual Workflow Builder"""
def __init__(self):
self.nom_zip = "visual_workflow_builder_propre_08jan2026.zip"
self.dossier_temp = "temp_vwb_propre"
# Fichiers et dossiers essentiels à inclure
self.fichiers_essentiels = [
# Backend
"visual_workflow_builder/backend/app.py",
"visual_workflow_builder/backend/requirements.txt",
"visual_workflow_builder/backend/api/__init__.py",
"visual_workflow_builder/backend/api/workflows.py",
"visual_workflow_builder/backend/api/screen_capture.py",
"visual_workflow_builder/backend/api/element_detection.py",
"visual_workflow_builder/backend/api/visual_targets.py",
"visual_workflow_builder/backend/api/real_demo.py",
"visual_workflow_builder/backend/api/errors.py",
"visual_workflow_builder/backend/api/templates.py",
"visual_workflow_builder/backend/api/node_types.py",
"visual_workflow_builder/backend/api/executions.py",
"visual_workflow_builder/backend/api/import_export.py",
"visual_workflow_builder/backend/api/websocket_handlers.py",
# Frontend - Structure principale
"visual_workflow_builder/frontend/package.json",
"visual_workflow_builder/frontend/webpack.config.js",
"visual_workflow_builder/frontend/tsconfig.json",
"visual_workflow_builder/frontend/src/index.tsx",
"visual_workflow_builder/frontend/src/App.tsx",
"visual_workflow_builder/frontend/src/App.css",
# Composants React essentiels
"visual_workflow_builder/frontend/src/components/Canvas/index.tsx",
"visual_workflow_builder/frontend/src/components/Canvas/Canvas.css",
"visual_workflow_builder/frontend/src/components/Palette/index.tsx",
"visual_workflow_builder/frontend/src/components/Palette/Palette.css",
"visual_workflow_builder/frontend/src/components/PropertiesPanel/index.tsx",
"visual_workflow_builder/frontend/src/components/PropertiesPanel/PropertiesPanel.css",
"visual_workflow_builder/frontend/src/components/RealScreenCapture/index.tsx",
"visual_workflow_builder/frontend/src/components/RealScreenCapture/RealScreenCapture.css",
"visual_workflow_builder/frontend/src/components/VisualPropertiesPanel/index.tsx",
"visual_workflow_builder/frontend/src/components/VisualPropertiesPanel/VisualPropertiesPanel.css",
"visual_workflow_builder/frontend/src/components/VisualScreenSelector/index.tsx",
"visual_workflow_builder/frontend/src/components/VisualScreenSelector/VisualScreenSelector.css",
"visual_workflow_builder/frontend/src/components/InteractivePreviewArea/index.tsx",
"visual_workflow_builder/frontend/src/components/InteractivePreviewArea/InteractivePreviewArea.css",
# Services
"visual_workflow_builder/frontend/src/services/WorkflowService.ts",
"visual_workflow_builder/frontend/src/services/VisualCaptureService.ts",
"visual_workflow_builder/frontend/src/services/WebSocketService.ts",
# Types et hooks
"visual_workflow_builder/frontend/src/types/index.ts",
"visual_workflow_builder/frontend/src/hooks/useWorkflow.ts",
"visual_workflow_builder/frontend/src/hooks/useSelection.ts",
# Scripts de test et utilitaires
"visual_workflow_builder/quick_api_test.py",
"visual_workflow_builder/test_api_connections_fixed.py",
"visual_workflow_builder/test_real_demo.py",
"visual_workflow_builder/test_documentation_browser_real.py",
"visual_workflow_builder/test_documentation_simple.py",
# Documentation
"visual_workflow_builder/README.md",
"visual_workflow_builder/docs/TROUBLESHOOTING.md",
"visual_workflow_builder/docs/VISUAL_SELECTION_GUIDE.md",
"visual_workflow_builder/GUIDE_TESTS_UTILISATEUR.md",
"visual_workflow_builder/README_DEMO_REELLE.md",
"visual_workflow_builder/README_DEMONSTRATION_REELLE.md",
"visual_workflow_builder/PHASE_2_FINALIZATION_COMPLETE.md",
]
# Scripts de diagnostic et utilitaires racine
self.scripts_utilitaires = [
"diagnostic_backend_complet.py",
"demarrer_backend_propre.py",
"test_systeme_complet.py",
]
# Documentation de référence
self.docs_reference = [
"LOCALISATION_REALDEMO_COMPLETE_08JAN2026.md",
"VISUAL_WORKFLOW_BUILDER_VISION_REFACTOR_COMPLETE.md",
"RPA_SYSTEM_UNIFICATION_TASK1_COMPLETE.md",
]
def creer_dossier_temp(self):
"""Créer le dossier temporaire"""
if os.path.exists(self.dossier_temp):
shutil.rmtree(self.dossier_temp)
os.makedirs(self.dossier_temp)
print(f"📁 Dossier temporaire créé: {self.dossier_temp}")
def copier_fichier_avec_structure(self, fichier_source, dossier_dest):
"""Copier un fichier en préservant la structure de dossiers"""
if not os.path.exists(fichier_source):
print(f" ⚠️ Fichier manquant: {fichier_source}")
return False
# Créer la structure de dossiers dans le dossier de destination
chemin_relatif = os.path.dirname(fichier_source)
dossier_cible = os.path.join(dossier_dest, chemin_relatif)
os.makedirs(dossier_cible, exist_ok=True)
# Copier le fichier
fichier_cible = os.path.join(dossier_dest, fichier_source)
shutil.copy2(fichier_source, fichier_cible)
print(f"{fichier_source}")
return True
def verifier_conformite_fichier(self, chemin_fichier):
"""Vérifier la conformité française d'un fichier"""
try:
with open(chemin_fichier, 'r', encoding='utf-8') as f:
contenu = f.read()
# Vérifier l'attribution
if ('Auteur : Dom, Alice, Kiro' in contenu or 'Auteur: Dom, Alice, Kiro' in contenu) and '8 janvier 2026' in contenu:
return True
# Pour les fichiers sans attribution (JSON, config, etc.)
extension = os.path.splitext(chemin_fichier)[1].lower()
if extension in ['.json', '.md', '.txt', '.yml', '.yaml']:
return True
return False
except:
return True # Fichiers binaires ou non lisibles
def corriger_attribution_si_necessaire(self, chemin_fichier):
"""Corriger l'attribution d'un fichier si nécessaire"""
if self.verifier_conformite_fichier(chemin_fichier):
return
try:
with open(chemin_fichier, 'r', encoding='utf-8') as f:
contenu = f.read()
extension = os.path.splitext(chemin_fichier)[1].lower()
# Ajouter l'attribution selon le type de fichier
if extension == '.py':
if not contenu.startswith('#!/usr/bin/env python3'):
attribution = '#!/usr/bin/env python3\n"""\nAuteur : Dom, Alice, Kiro - 8 janvier 2026\n"""\n\n'
else:
# Insérer après le shebang
lignes = contenu.split('\n')
lignes.insert(1, '"""')
lignes.insert(2, 'Auteur : Dom, Alice, Kiro - 8 janvier 2026')
lignes.insert(3, '"""')
contenu = '\n'.join(lignes)
elif extension in ['.ts', '.tsx', '.js', '.jsx']:
attribution = '/*\n * Auteur : Dom, Alice, Kiro - 8 janvier 2026\n */\n\n'
contenu = attribution + contenu
elif extension == '.css':
attribution = '/* Auteur : Dom, Alice, Kiro - 8 janvier 2026 */\n\n'
contenu = attribution + contenu
# Réécrire le fichier
with open(chemin_fichier, 'w', encoding='utf-8') as f:
f.write(contenu)
print(f" 🔧 Attribution corrigée: {chemin_fichier}")
except Exception as e:
print(f" ❌ Erreur correction {chemin_fichier}: {e}")
def copier_fichiers_essentiels(self):
"""Copier tous les fichiers essentiels"""
print("📋 Copie des fichiers essentiels...")
fichiers_copies = 0
# Fichiers du VWB
for fichier in self.fichiers_essentiels:
if self.copier_fichier_avec_structure(fichier, self.dossier_temp):
fichier_cible = os.path.join(self.dossier_temp, fichier)
self.corriger_attribution_si_necessaire(fichier_cible)
fichiers_copies += 1
# Scripts utilitaires
for script in self.scripts_utilitaires:
if self.copier_fichier_avec_structure(script, self.dossier_temp):
fichier_cible = os.path.join(self.dossier_temp, script)
self.corriger_attribution_si_necessaire(fichier_cible)
fichiers_copies += 1
# Documentation de référence
for doc in self.docs_reference:
if self.copier_fichier_avec_structure(doc, self.dossier_temp):
fichiers_copies += 1
print(f"📊 Total fichiers copiés: {fichiers_copies}")
return fichiers_copies
def creer_readme_principal(self):
"""Créer un README principal pour le ZIP"""
readme_contenu = """# Visual Workflow Builder - Version Propre
**Auteur : Dom, Alice, Kiro - 8 janvier 2026**
## 📋 Contenu de cette archive
Cette archive contient une version propre et organisée du Visual Workflow Builder avec :
### 🏗️ Backend (Flask)
- `visual_workflow_builder/backend/` - Serveur API Flask complet
- Scripts de démarrage et diagnostic inclus
### 🎨 Frontend (React + TypeScript)
- `visual_workflow_builder/frontend/` - Interface utilisateur React
- Composants Material-UI avec design system cohérent
- Services de capture d'écran et détection d'éléments
### 🧪 Scripts de Test
- `diagnostic_backend_complet.py` - Diagnostic complet du backend
- `demarrer_backend_propre.py` - Démarrage propre du serveur
- `test_systeme_complet.py` - Tests système complets
- `visual_workflow_builder/quick_api_test.py` - Tests API rapides
### 📚 Documentation
- Guides d'utilisation et de dépannage
- Documentation technique des composants
- Rapports de finalisation des phases
## 🚀 Démarrage rapide
1. **Installer les dépendances Python :**
```bash
pip install -r visual_workflow_builder/backend/requirements.txt
```
2. **Démarrer le backend :**
```bash
python3 demarrer_backend_propre.py
```
3. **Tester le système :**
```bash
python3 test_systeme_complet.py
```
4. **Installer les dépendances Frontend :**
```bash
cd visual_workflow_builder/frontend
npm install
```
5. **Démarrer le frontend :**
```bash
npm start
```
## 🔧 Configuration
- Backend : Port 5002 (configurable via variable PORT)
- Frontend : Port 3000 (webpack dev server)
- Base de données : SQLite (workflows.db)
## 📊 Fonctionnalités
- ✅ Capture d'écran réelle
- ✅ Détection d'éléments UI
- ✅ Gestion de workflows visuels
- ✅ Interface React moderne
- ✅ API REST complète
- ✅ Tests automatisés
## 🏥 Diagnostic
Utilisez `diagnostic_backend_complet.py` pour vérifier l'état du système.
## 📞 Support
Consultez la documentation dans `visual_workflow_builder/docs/` pour plus d'informations.
---
*Version générée le 8 janvier 2026*
"""
with open(os.path.join(self.dossier_temp, "README.md"), 'w', encoding='utf-8') as f:
f.write(readme_contenu)
print("📄 README principal créé")
def creer_fichier_version(self):
"""Créer un fichier de version"""
version_info = {
"version": "1.0.0",
"date_creation": "2026-01-08",
"auteurs": ["Dom", "Alice", "Kiro"],
"description": "Visual Workflow Builder - Version propre et organisée",
"composants": {
"backend": "Flask API Server",
"frontend": "React + TypeScript UI",
"tests": "Scripts de test automatisés",
"docs": "Documentation complète"
},
"conformite": {
"langue": "français",
"attribution": "Dom, Alice, Kiro - 8 janvier 2026",
"tests_reels": True,
"organisation": "docs/ et tests/ centralisés"
}
}
with open(os.path.join(self.dossier_temp, "version.json"), 'w', encoding='utf-8') as f:
json.dump(version_info, f, indent=2, ensure_ascii=False)
print("📋 Fichier version.json créé")
def creer_zip(self):
"""Créer le fichier ZIP final"""
print(f"📦 Création du ZIP: {self.nom_zip}")
with zipfile.ZipFile(self.nom_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(self.dossier_temp):
for file in files:
chemin_fichier = os.path.join(root, file)
chemin_archive = os.path.relpath(chemin_fichier, self.dossier_temp)
zipf.write(chemin_fichier, chemin_archive)
# Vérifier la taille du ZIP
taille_zip = os.path.getsize(self.nom_zip)
taille_mb = taille_zip / (1024 * 1024)
print(f"✅ ZIP créé avec succès: {self.nom_zip}")
print(f"📏 Taille: {taille_mb:.2f} MB")
return True
def nettoyer_dossier_temp(self):
"""Nettoyer le dossier temporaire"""
if os.path.exists(self.dossier_temp):
shutil.rmtree(self.dossier_temp)
print(f"🧹 Dossier temporaire supprimé: {self.dossier_temp}")
def executer_creation_complete(self):
"""Exécuter la création complète du ZIP"""
print("📦 CRÉATION DU ZIP VWB PROPRE")
print("=" * 50)
try:
# Étape 1: Créer le dossier temporaire
self.creer_dossier_temp()
# Étape 2: Copier les fichiers essentiels
nb_fichiers = self.copier_fichiers_essentiels()
if nb_fichiers == 0:
print("❌ Aucun fichier copié - arrêt")
return False
# Étape 3: Créer les fichiers de documentation
self.creer_readme_principal()
self.creer_fichier_version()
# Étape 4: Créer le ZIP
succes = self.creer_zip()
# Étape 5: Nettoyer
self.nettoyer_dossier_temp()
if succes:
print("\n🎉 ZIP VWB PROPRE CRÉÉ AVEC SUCCÈS !")
print(f"📁 Fichier: {self.nom_zip}")
print(f"📊 Contenu: {nb_fichiers} fichiers + documentation")
print("✅ Conformité française respectée")
print("✅ Attribution des auteurs ajoutée")
print("✅ Tests réels uniquement")
return True
else:
print("\n❌ Échec de la création du ZIP")
return False
except Exception as e:
print(f"\n💥 Erreur critique: {e}")
self.nettoyer_dossier_temp()
return False
def main():
"""Fonction principale"""
createur = CreateurZipVWB()
try:
succes = createur.executer_creation_complete()
return 0 if succes else 1
except KeyboardInterrupt:
print("\n⚠️ Création interrompue par l'utilisateur")
createur.nettoyer_dossier_temp()
return 2
except Exception as e:
print(f"\n💥 Erreur: {e}")
return 3
if __name__ == "__main__":
import sys
sys.exit(main())

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +0,0 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "agent_v0"))
from user_config import load_user_config
import json
print("=== Debug agent config ===")
# Load config
config = load_user_config()
print(f"Config loaded: {config}")
# Check specific keys
print(f"enable_encryption: {config.get('enable_encryption')}")
print(f"encryption_password: {config.get('encryption_password')}")
# Check config file directly
with open("agent_config.json", 'r') as f:
direct_config = json.load(f)
print(f"Direct config file: {direct_config}")

View File

@@ -1,95 +0,0 @@
#!/usr/bin/env python3
import sys
sys.path.insert(0, '.')
# Test d'exécution ligne par ligne
try:
print("1. Importing modules...")
import logging
from collections import defaultdict
from datetime import datetime, timedelta
from typing import Dict, List, Any
from core.system.models import SimpleFailureEvent
print("✓ All imports successful")
print("2. Creating logger...")
logger = logging.getLogger(__name__)
print("✓ Logger created")
print("3. Defining SimpleFailureWindow...")
class SimpleFailureWindow:
def __init__(self, window_start: datetime, window_duration_s: int, failures: List = None):
self.window_start = window_start
self.window_duration_s = window_duration_s
self.failures = failures or []
def add_failure(self, failure: SimpleFailureEvent) -> None:
self.failures.append(failure)
self.cleanup_expired()
def get_failure_count(self) -> int:
self.cleanup_expired()
return len(self.failures)
def cleanup_expired(self) -> None:
now = datetime.now()
cutoff = now - timedelta(seconds=self.window_duration_s)
self.failures = [f for f in self.failures if f.timestamp >= cutoff]
print("✓ SimpleFailureWindow defined")
print("4. Defining CircuitBreaker...")
class CircuitBreaker:
def __init__(self, policy: Dict[str, Any]):
self.policy = policy
self.step_consecutive_failures: Dict[str, List] = defaultdict(list)
self.workflow_windows: Dict[str, SimpleFailureWindow] = {}
self.global_window = SimpleFailureWindow(
window_start=datetime.now(),
window_duration_s=policy.get('workflow_fail_window_s', 600),
failures=[]
)
self.step_success_counts: Dict[str, int] = defaultdict(int)
logger.info("CircuitBreaker initialized")
def record_failure(self, workflow_id: str, step_id: str, failure_type: str) -> None:
now = datetime.now()
step_key = f"{workflow_id}:{step_id}"
failure_event = SimpleFailureEvent(
timestamp=now,
workflow_id=workflow_id,
step_id=step_id,
failure_type=failure_type
)
self.step_consecutive_failures[step_key].append(failure_event)
self.step_success_counts[step_key] = 0
if workflow_id not in self.workflow_windows:
self.workflow_windows[workflow_id] = SimpleFailureWindow(
window_start=now,
window_duration_s=self.policy.get('workflow_fail_window_s', 600),
failures=[]
)
self.workflow_windows[workflow_id].add_failure(failure_event)
self.global_window.add_failure(failure_event)
print("✓ CircuitBreaker defined")
print("5. Testing CircuitBreaker...")
cb = CircuitBreaker({'test': True})
print("✓ CircuitBreaker instance created")
print("Available methods:", [method for method in dir(cb) if not method.startswith('_')])
print("Has record_failure:", hasattr(cb, 'record_failure'))
if hasattr(cb, 'record_failure'):
print("6. Testing record_failure...")
cb.record_failure("test_workflow", "test_step", "TEST_ERROR")
print("✓ record_failure works")
except Exception as e:
print(f"✗ Error: {e}")
import traceback
traceback.print_exc()

View File

@@ -1,56 +0,0 @@
exc()t_back.prinrace ttraceback
import e}")
ur: { Erre" print(f:
tion as ept Excep
exce_1'])}")low:step'test_workfs[ive_failuresecutp_con(cb.steenutifs: {lonséc"📊 Échecs c print(f
)
gistré"chec enret(" É
prin")T_NOT_FOUNDRGE, "TA"tep_1", "s_workflowestfailure("t.record_ cbple...")
n échec simest d'ut("\n🧪 T
prin QUANT")
r}: MAN {att nt(f" pri else:
e}")
alu)} = {vvaluee(r}: {typ"{att print(f , attr)
(cbattr value = get :
attr)cb, tr( hasat if s:
ed_attrequirn r for attr i ]
w'
indo 'global_w
_windows',workflow ' ,
_counts'cesstep_suc 's',
ailurestive_fep_consecu 'st
policy', ' [
attrs =equired_ r)
is:"s requ attributcation desfi"\n🔍 Vériint(
pr")
ttr}t(f" - {a prin '):
ith('_rtsw attr.sta if notb):
ir(cin d for attr s:")
s disponible🔍 Attributprint("
éé")aker crrcuitBre" Ci
print(er(policy)akrercuitB
cb = Ci.")r..reakecuitBion du Cirréatnt("📋 C pri}
shold': 2
_threess_reset 'succ,
_window': 30_inl_maxglobal_fai ': 10,
w'ndol_max_in_wifailow_orkf'w
600,dow_s':w_fail_winworkflo 'ed': 3,
ak_to_degradrestl_'step_fai{
y = polic
ssi")er réutBreakt Circuior Impint(" prer
reaktBort Circuier imprcuit_breakre.system.cifrom cotry:
')
pend('.
sys.path.aprt sys"
impo
""rcuitBreake pour Cirbug simple"
De""ython3
n/env psr/bi#!/u

View File

@@ -1,109 +0,0 @@
#!/usr/bin/env python3
"""
Debug script to analyze the encryption/decryption issue between agent and server.
"""
import os
import sys
from pathlib import Path
# Add paths for imports
sys.path.insert(0, str(Path(__file__).parent / "agent_v0"))
sys.path.insert(0, str(Path(__file__).parent / "server"))
def debug_encryption_issue():
"""Debug the encryption/decryption mismatch."""
print("=== Debugging Encryption Issue ===")
# Load environment
env_local_path = Path(".env.local")
if env_local_path.exists():
with open(env_local_path, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ[key.strip()] = value.strip()
password = os.getenv("ENCRYPTION_PASSWORD")
print(f"Using password: {password[:16]}...")
# Find the most recent .enc file
agent_sessions_dir = Path("agent_v0/sessions")
enc_files = list(agent_sessions_dir.glob("*.enc"))
if not enc_files:
print("❌ No .enc files found in agent_v0/sessions/")
return False
# Get the most recent .enc file
latest_enc = max(enc_files, key=lambda p: p.stat().st_mtime)
print(f"Testing with file: {latest_enc}")
print(f"File size: {latest_enc.stat().st_size} bytes")
# Test with agent's decryption function
print("\n--- Testing Agent's Decryption ---")
try:
from storage_encrypted import decrypt_session_file as agent_decrypt
agent_result = agent_decrypt(str(latest_enc), password)
print(f"✅ Agent decryption successful: {agent_result}")
# Check if it's a valid ZIP
import zipfile
with zipfile.ZipFile(agent_result, 'r') as zf:
files = zf.namelist()
print(f" ZIP contains {len(files)} files")
except Exception as e:
print(f"❌ Agent decryption failed: {e}")
return False
# Test with server's decryption function
print("\n--- Testing Server's Decryption ---")
try:
# Import server's version
import importlib.util
spec = importlib.util.spec_from_file_location("server_storage", "server/storage_encrypted.py")
server_storage = importlib.util.module_from_spec(spec)
spec.loader.exec_module(server_storage)
server_result = server_storage.decrypt_session_file(str(latest_enc), password)
print(f"✅ Server decryption successful: {server_result}")
# Check if it's a valid ZIP
with zipfile.ZipFile(server_result, 'r') as zf:
files = zf.namelist()
print(f" ZIP contains {len(files)} files")
except Exception as e:
print(f"❌ Server decryption failed: {e}")
print(f" Error type: {type(e).__name__}")
print(f" Error details: {str(e)}")
# Let's analyze the file structure
print("\n--- Analyzing File Structure ---")
try:
with open(latest_enc, 'rb') as f:
salt = f.read(16)
iv = f.read(16)
ciphertext = f.read()
print(f"Salt length: {len(salt)} bytes")
print(f"IV length: {len(iv)} bytes")
print(f"Ciphertext length: {len(ciphertext)} bytes")
print(f"Total file size: {16 + 16 + len(ciphertext)} bytes")
if len(ciphertext) % 16 != 0:
print(f"⚠️ Ciphertext length not multiple of 16: {len(ciphertext) % 16} remainder")
else:
print("✅ Ciphertext length is multiple of 16")
except Exception as e2:
print(f"❌ Error analyzing file: {e2}")
return False
print("\n✅ Both agent and server decryption work!")
return True
if __name__ == "__main__":
success = debug_encryption_issue()
sys.exit(0 if success else 1)

View File

@@ -1,52 +0,0 @@
#!/usr/bin/env python3
"""
Simple test to debug the encryption issue.
"""
import os
import sys
from pathlib import Path
# Load environment
env_local_path = Path(".env.local")
if env_local_path.exists():
print(f"Loading environment from {env_local_path}")
with open(env_local_path, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ[key.strip()] = value.strip()
print("Environment loaded:")
print(f"ENCRYPTION_PASSWORD: {os.getenv('ENCRYPTION_PASSWORD', 'NOT_SET')[:20]}...")
print(f"RPA_SERVER_URL: {os.getenv('RPA_SERVER_URL', 'NOT_SET')}")
print(f"RPA_AUTH_DISABLED: {os.getenv('RPA_AUTH_DISABLED', 'NOT_SET')}")
# Add paths
sys.path.insert(0, str(Path(__file__).parent / "agent_v0"))
try:
from user_config import load_user_config
config = load_user_config()
print(f"\nAgent config:")
print(f" encryption_password: {config.get('encryption_password')}")
print(f" enable_encryption: {config.get('enable_encryption')}")
# This is the key issue - when encryption_password is None,
# the agent uses session_id as password
if config.get('encryption_password') is None:
print(" -> Agent will use session-based password (rpa_vision_v3_<session_id>)")
server_password = os.getenv("ENCRYPTION_PASSWORD", "rpa_vision_v3_default_key")
agent_password_type = "session-based" if config.get('encryption_password') is None else "fixed"
print(f"\nPassword mismatch:")
print(f" Agent uses: {agent_password_type} password")
print(f" Server uses: fixed password from ENCRYPTION_PASSWORD")
print(f" This causes 'Padding invalide' error when server tries to decrypt")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()

View File

@@ -1,32 +0,0 @@
#!/usr/bin/env python3
"""
Script pour tester la fonction _is_public
"""
import sys
from pathlib import Path
# Ajouter le répertoire parent au path
sys.path.insert(0, str(Path(__file__).parent))
from core.security.fastapi_security import DEFAULT_PUBLIC_PATHS
def test_is_public():
print("=== Test des endpoints publics ===")
print(f"DEFAULT_PUBLIC_PATHS: {DEFAULT_PUBLIC_PATHS}")
test_paths = [
"/healthz",
"/api/traces/debug-auth",
"/api/traces/debug-env",
"/api/traces/upload",
"/metrics",
"/",
]
for path in test_paths:
is_public = path in DEFAULT_PUBLIC_PATHS
print(f"{path}: {'✅ PUBLIC' if is_public else '❌ PRIVÉ'}")
if __name__ == "__main__":
test_is_public()

View File

@@ -1,64 +0,0 @@
#!/usr/bin/env python3
"""
Script de debug pour tester l'authentification du serveur
"""
import os
import sys
from pathlib import Path
# Ajouter le répertoire parent au path
sys.path.insert(0, str(Path(__file__).parent))
from core.security.api_tokens import TokenManager, validate_token
def main():
print("=== Debug Authentification Serveur ===")
# Vérifier les variables d'environnement
print("\n1. Variables d'environnement:")
admin_token = os.getenv('RPA_TOKEN_ADMIN')
readonly_token = os.getenv('RPA_TOKEN_READONLY')
print(f"RPA_TOKEN_ADMIN: {admin_token[:8] + '...' if admin_token else 'NON DÉFINI'}")
print(f"RPA_TOKEN_READONLY: {readonly_token[:8] + '...' if readonly_token else 'NON DÉFINI'}")
# Tester le TokenManager
print("\n2. Test TokenManager:")
try:
tm = TokenManager()
print(f"Admin tokens: {len(tm.admin_tokens)}")
print(f"Read-only tokens: {len(tm.read_only_tokens)}")
# Tester la validation du token admin
if admin_token:
print(f"\n3. Test validation token admin:")
try:
token_info = tm.validate_token(admin_token)
print(f"✅ Token admin valide: {token_info.role}")
except Exception as e:
print(f"❌ Token admin invalide: {e}")
# Tester la validation du token readonly
if readonly_token:
print(f"\n4. Test validation token readonly:")
try:
token_info = tm.validate_token(readonly_token)
print(f"✅ Token readonly valide: {token_info.role}")
except Exception as e:
print(f"❌ Token readonly invalide: {e}")
except Exception as e:
print(f"❌ Erreur TokenManager: {e}")
# Tester la fonction validate_token directement
print(f"\n5. Test fonction validate_token directe:")
if admin_token:
try:
token_info = validate_token(admin_token)
print(f"✅ validate_token réussie: {token_info.role}")
except Exception as e:
print(f"❌ validate_token échouée: {e}")
if __name__ == "__main__":
main()

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env python3
"""
Debug simple pour tester l'import du module de déchiffrement côté serveur.
"""
import sys
from pathlib import Path
# Ajouter le répertoire parent au path comme le fait le serveur
sys.path.insert(0, str(Path(__file__).parent))
print("=== Debug import serveur ===")
print(f"Python path: {sys.path[:3]}")
try:
from agent_v0.storage_encrypted import decrypt_session_file
print("✅ Import agent_v0.storage_encrypted réussi")
print(f" Fonction decrypt_session_file: {decrypt_session_file}")
except ImportError as e:
print(f"❌ Erreur import agent_v0.storage_encrypted: {e}")
try:
from storage_encrypted import decrypt_session_file
print("✅ Import storage_encrypted réussi")
except ImportError as e:
print(f"❌ Erreur import storage_encrypted: {e}")

View File

@@ -1,35 +0,0 @@
#!/usr/bin/env python3
"""
Script simple pour tester les paths publics
"""
DEFAULT_PUBLIC_PATHS = {
"/healthz",
"/metrics",
"/",
"/docs",
"/redoc",
"/openapi.json",
"/api/traces/debug-auth", # Debug endpoint
"/api/traces/debug-env", # Debug endpoint
}
def test_is_public():
print("=== Test des endpoints publics ===")
print(f"DEFAULT_PUBLIC_PATHS: {DEFAULT_PUBLIC_PATHS}")
test_paths = [
"/healthz",
"/api/traces/debug-auth",
"/api/traces/debug-env",
"/api/traces/upload",
"/metrics",
"/",
]
for path in test_paths:
is_public = path in DEFAULT_PUBLIC_PATHS
print(f"{path}: {'✅ PUBLIC' if is_public else '❌ PRIVÉ'}")
if __name__ == "__main__":
test_is_public()

View File

@@ -1,187 +0,0 @@
#!/usr/bin/env python3
"""
Script de démarrage propre du backend VWB
Auteur : Dom, Alice, Kiro - 8 janvier 2026
"""
import os
import sys
import subprocess
import time
import signal
import psutil
def nettoyer_processus_backend():
"""Nettoyer tous les processus backend existants"""
print("🧹 Nettoyage des processus backend existants...")
processus_tues = 0
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
try:
cmdline = ' '.join(proc.info['cmdline'] or [])
if any(pattern in cmdline for pattern in [
'visual_workflow_builder/backend/app.py',
'web_dashboard/app.py',
'flask',
':5001',
':5002'
]):
print(f" 🔪 Arrêt du processus PID {proc.info['pid']}: {proc.info['name']}")
proc.terminate()
processus_tues += 1
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
if processus_tues > 0:
print(f"{processus_tues} processus arrêtés")
time.sleep(3) # Attendre que les processus se terminent proprement
else:
print(" Aucun processus backend trouvé")
def verifier_port_libre(port):
"""Vérifier si un port est libre"""
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind(('localhost', port))
return True
except OSError:
return False
def demarrer_backend():
"""Démarrer le backend VWB proprement"""
print("🚀 Démarrage du backend VWB...")
# Vérifier que le port 5002 est libre
if not verifier_port_libre(5002):
print("❌ Le port 5002 est occupé")
return None
# Variables d'environnement propres
env = os.environ.copy()
env.update({
'PORT': '5002',
'FLASK_ENV': 'development',
'FLASK_DEBUG': '1',
'PYTHONPATH': '.',
})
# Supprimer les variables qui pourraient causer des conflits
for var in ['FLASK_APP', 'FLASK_RUN_PORT', 'FLASK_RUN_HOST']:
env.pop(var, None)
# Commande de démarrage
cmd = [
'./venv_v3/bin/python',
'visual_workflow_builder/backend/app.py'
]
try:
print(f" 📝 Commande: {' '.join(cmd)}")
print(f" 🔧 Port: 5002")
print(f" 🌍 Environnement: development")
# Démarrer le processus
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=env,
cwd='.',
bufsize=1,
universal_newlines=True
)
print(f" 🆔 PID: {process.pid}")
# Surveiller le démarrage
print(" ⏳ Surveillance du démarrage...")
for i in range(30): # 30 secondes max
# Vérifier si le processus est encore vivant
if process.poll() is not None:
stdout, stderr = process.communicate()
print(f" ❌ Le processus s'est arrêté prématurément")
print(f" 📝 Sortie: {stdout}")
return None
# Lire la sortie
try:
line = process.stdout.readline()
if line:
print(f" 📄 {line.strip()}")
# Vérifier les indicateurs de succès
if any(indicator in line.lower() for indicator in [
'running on',
'serving flask app',
'debug mode: on'
]):
print(f" ✅ Backend démarré avec succès !")
return process
# Vérifier les erreurs
if any(error in line.lower() for error in [
'error',
'failed',
'exception',
'address already in use'
]):
print(f" ❌ Erreur détectée: {line.strip()}")
process.terminate()
return None
except:
pass
time.sleep(1)
print(" ⚠️ Timeout atteint, mais le processus semble fonctionner")
return process
except Exception as e:
print(f" ❌ Erreur lors du démarrage: {e}")
return None
def main():
"""Fonction principale"""
print("🏥 DÉMARRAGE PROPRE DU BACKEND VWB")
print("=" * 50)
try:
# Étape 1: Nettoyer les processus existants
nettoyer_processus_backend()
# Étape 2: Démarrer le backend
process = demarrer_backend()
if process:
print("\n✅ Backend démarré avec succès !")
print(f"🔗 URL: http://localhost:5002")
print(f"🆔 PID: {process.pid}")
print("\n📋 Commandes utiles:")
print(f" • Tester: python3 visual_workflow_builder/quick_api_test.py")
print(f" • Arrêter: kill {process.pid}")
print("\n⌨️ Appuyez sur Ctrl+C pour arrêter le serveur")
# Attendre l'interruption
try:
process.wait()
except KeyboardInterrupt:
print("\n🛑 Arrêt demandé par l'utilisateur")
process.terminate()
process.wait()
print("✅ Backend arrêté proprement")
else:
print("\n❌ Échec du démarrage du backend")
return 1
except Exception as e:
print(f"\n💥 Erreur critique: {e}")
return 2
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,92 +0,0 @@
#!/usr/bin/env python3
"""Demo script for RPA Analytics System."""
import time
from datetime import datetime, timedelta
from core.analytics.analytics_system import get_analytics_system
def demo_analytics():
"""Demonstrate analytics system capabilities."""
print("=" * 60)
print("RPA Analytics System Demo")
print("=" * 60)
# Initialize system
print("\n1. Initializing Analytics System...")
analytics = get_analytics_system()
print("✓ Analytics system initialized")
# Start resource monitoring (optional - requires psutil)
print("\n2. Starting Resource Monitoring...")
try:
analytics.start_resource_monitoring(interval_seconds=5)
print("✓ Resource monitoring started (5s interval)")
except Exception as e:
print(f"⚠ Resource monitoring not available: {e}")
print(" (This is optional - continuing without it)")
# Simulate some workflow executions
print("\n3. Simulating Workflow Executions...")
print(" (Skipping - requires full integration with ExecutionLoop)")
print(" ✓ Use demo_integrated_execution.py for full demo")
# Query metrics
print("\n4. Querying Metrics...")
print(" (Skipping - no data yet)")
print(" ✓ Query engine ready")
# Performance analysis
print("\n5. Analyzing Performance...")
print(" (Skipping - no data yet)")
print(" ✓ Performance analyzer ready")
# All components ready
print("\n5. All Analytics Components Ready!")
print(" ✓ Performance Analyzer")
print(" ✓ Anomaly Detector")
print(" ✓ Insight Generator")
print(" ✓ Success Rate Calculator")
print(" ✓ Report Generator")
print(" ✓ Dashboard Manager")
print(" ✓ Real-time Analytics")
print(" ✓ Query Engine")
# System stats
print("\n6. System Statistics...")
try:
stats = analytics.get_system_stats()
print(f" ✓ System stats available")
except Exception as e:
print(f" ⚠ Stats not available: {e}")
print("\n" + "=" * 60)
print("Demo Complete!")
print("=" * 60)
print("\n✅ Analytics System Successfully Initialized!")
print("\nAll components are ready:")
print(" • Metrics Collection")
print(" • Performance Analysis")
print(" • Anomaly Detection")
print(" • Insight Generation")
print(" • Report Generation")
print(" • Dashboard Management")
print(" • Real-time Tracking")
print("\nNext Steps:")
print(" 1. Run: python3 demo_integrated_execution.py")
print(" 2. See: ANALYTICS_INTEGRATION_GUIDE.md")
print(" 3. See: ANALYTICS_QUICKSTART.md")
print(" 4. Integrate with your ExecutionLoop")
print("\n💡 Tip: Use demo_integrated_execution.py for a full working demo!")
if __name__ == '__main__':
try:
demo_analytics()
except KeyboardInterrupt:
print("\n\nDemo interrupted by user")
except Exception as e:
print(f"\n\nError during demo: {e}")
import traceback
traceback.print_exc()

View File

@@ -1,103 +0,0 @@
#!/usr/bin/env python3
"""
Démonstration de l'automatisation RPA Vision V3
Ce script montre comment créer des chaînes et triggers automatiques.
"""
import sys
from pathlib import Path
sys.path.insert(0, '.')
from core.monitoring.chain_manager import ChainManager
from core.monitoring.trigger_manager import TriggerManager
# Initialiser les managers
print("🚀 Initialisation...")
cm = ChainManager(Path('data/chains'))
tm = TriggerManager(Path('data/triggers'))
print("\n" + "="*60)
print("DÉMONSTRATION : Automatisation RPA Vision V3")
print("="*60)
# 1. Créer une chaîne de test
print("\n📋 1. Création d'une chaîne de workflows...")
try:
chain = cm.create_chain(
name="Demo Backup Chain",
workflows=["wf_export", "wf_compress", "wf_upload"]
)
print(f" ✅ Chaîne créée: {chain.chain_id}")
print(f" 📝 Nom: {chain.name}")
print(f" 🔄 Workflows: {''.join(chain.workflows)}")
except Exception as e:
print(f" ⚠️ Erreur: {e}")
# 2. Créer un trigger schedule
print("\n⏰ 2. Création d'un trigger schedule...")
try:
trigger_schedule = tm.create_trigger(
trigger_type="schedule",
workflow_id=chain.chain_id,
config={
"interval_seconds": 300 # Toutes les 5 minutes
}
)
print(f" ✅ Trigger créé: {trigger_schedule.trigger_id}")
print(f" ⏱️ Intervalle: 300 secondes (5 minutes)")
print(f" 🎯 Cible: {trigger_schedule.workflow_id}")
print(f" 🟢 Activé: {trigger_schedule.enabled}")
except Exception as e:
print(f" ⚠️ Erreur: {e}")
# 3. Créer un trigger file
print("\n📁 3. Création d'un trigger file...")
try:
trigger_file = tm.create_trigger(
trigger_type="file",
workflow_id="wf_process_invoice",
config={
"watch_directory": "/tmp/rpa_test",
"file_pattern": "*.txt"
}
)
print(f" ✅ Trigger créé: {trigger_file.trigger_id}")
print(f" 📂 Répertoire: /tmp/rpa_test")
print(f" 🔍 Pattern: *.txt")
print(f" 🎯 Workflow: {trigger_file.workflow_id}")
except Exception as e:
print(f" ⚠️ Erreur: {e}")
# 4. Lister toutes les chaînes
print("\n📊 4. Chaînes configurées:")
chains = cm.list_chains()
for c in chains:
print(f"{c.name} ({c.chain_id})")
print(f" Workflows: {len(c.workflows)}")
print(f" Taux de succès: {c.success_rate:.1f}%")
# 5. Lister tous les triggers
print("\n⚡ 5. Triggers configurés:")
triggers = tm.list_triggers()
for t in triggers:
status = "🟢 Activé" if t.enabled else "🔴 Désactivé"
print(f"{t.trigger_type.upper()} - {status}")
print(f" ID: {t.trigger_id}")
print(f" Cible: {t.workflow_id}")
print(f" Déclenchements: {t.fire_count}")
print("\n" + "="*60)
print("✨ AUTOMATISATION CONFIGURÉE")
print("="*60)
print("\n📌 Prochaines étapes:")
print(" 1. Le scheduler tourne en arrière-plan")
print(" 2. Les triggers schedule se déclenchent automatiquement")
print(" 3. Les triggers file surveillent les répertoires")
print(" 4. Les chaînes s'exécutent séquentiellement")
print("\n🌐 Interface admin: http://localhost:5001")
print(" → Onglet 'Chaînes' pour voir les chaînes")
print(" → Onglet 'Déclencheurs' pour gérer les triggers")
print(" → Onglet 'Métriques' pour voir le statut du scheduler")
print("\n🎉 Tout est prêt pour l'automatisation !")

View File

@@ -1,535 +0,0 @@
#!/usr/bin/env python3
"""
Complete demonstration of Enhanced Agent V0 features
This script demonstrates all the enhanced Agent V0 features working together
in a realistic workflow capture scenario.
"""
import sys
import os
import time
import tempfile
from datetime import datetime
from typing import List
# Add agent_v0 to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'agent_v0'))
from agent_v0.enhanced_raw_session import EnhancedRawSession
from agent_v0.workflow_namer import WorkflowNamer
from agent_v0.enhanced_event_captor import EnhancedEventCaptor, UIContext
from agent_v0.targeted_screen_capturer import TargetedScreenCapturer
from agent_v0.processing_monitor import ProcessingMonitor, ProcessingStage
from agent_v0.workflow_locator import WorkflowLocator, WorkflowFilters
def simulate_customer_registration_workflow():
"""Simulate a complete customer registration workflow"""
print("🎯 Simulating Customer Registration Workflow")
print("=" * 50)
# 1. Create enhanced session with intelligent naming
print("1. Creating enhanced session...")
session = EnhancedRawSession.create_enhanced(
user_id="demo_user",
user_label="Demo User",
workflow_name=None, # Let system generate intelligent name
auto_generate_name=True,
platform="linux",
hostname="demo-machine",
screen_resolution=[1920, 1080]
)
print(f" Session ID: {session.session_id}")
# 2. Simulate workflow steps with enhanced events
print("\n2. Capturing enhanced workflow events...")
workflow_steps = [
# Login sequence
{
"action": "click",
"pos": [960, 400],
"window": "CRM Pro - Login",
"app": "CRM_Pro",
"element_type": "input",
"element_text": "Username",
"description": "Click username field"
},
{
"action": "type",
"text": "admin",
"window": "CRM Pro - Login",
"app": "CRM_Pro",
"description": "Type username"
},
{
"action": "click",
"pos": [960, 450],
"window": "CRM Pro - Login",
"app": "CRM_Pro",
"element_type": "input",
"element_text": "Password",
"description": "Click password field"
},
{
"action": "type",
"text": "password123",
"window": "CRM Pro - Login",
"app": "CRM_Pro",
"description": "Type password (will be masked)"
},
{
"action": "click",
"pos": [960, 500],
"window": "CRM Pro - Login",
"app": "CRM_Pro",
"element_type": "button",
"element_text": "Login",
"description": "Click login button"
},
# Navigation to customer section
{
"action": "click",
"pos": [200, 150],
"window": "CRM Pro - Dashboard",
"app": "CRM_Pro",
"element_type": "link",
"element_text": "Customers",
"description": "Navigate to customers"
},
{
"action": "click",
"pos": [300, 100],
"window": "CRM Pro - Customer List",
"app": "CRM_Pro",
"element_type": "button",
"element_text": "Add New Customer",
"description": "Click add customer button"
},
# Customer form filling
{
"action": "click",
"pos": [400, 200],
"window": "CRM Pro - New Customer Form",
"app": "CRM_Pro",
"element_type": "input",
"element_text": "First Name",
"description": "Click first name field"
},
{
"action": "type",
"text": "John",
"window": "CRM Pro - New Customer Form",
"app": "CRM_Pro",
"description": "Type first name"
},
{
"action": "click",
"pos": [400, 250],
"window": "CRM Pro - New Customer Form",
"app": "CRM_Pro",
"element_type": "input",
"element_text": "Last Name",
"description": "Click last name field"
},
{
"action": "type",
"text": "Doe",
"window": "CRM Pro - New Customer Form",
"app": "CRM_Pro",
"description": "Type last name"
},
{
"action": "click",
"pos": [400, 300],
"window": "CRM Pro - New Customer Form",
"app": "CRM_Pro",
"element_type": "input",
"element_text": "Email",
"description": "Click email field"
},
{
"action": "type",
"text": "john.doe@example.com",
"window": "CRM Pro - New Customer Form",
"app": "CRM_Pro",
"description": "Type email address"
},
{
"action": "click",
"pos": [400, 350],
"window": "CRM Pro - New Customer Form",
"app": "CRM_Pro",
"element_type": "input",
"element_text": "Phone",
"description": "Click phone field"
},
{
"action": "type",
"text": "+1-555-123-4567",
"window": "CRM Pro - New Customer Form",
"app": "CRM_Pro",
"description": "Type phone number"
},
# Save customer
{
"action": "click",
"pos": [500, 450],
"window": "CRM Pro - New Customer Form",
"app": "CRM_Pro",
"element_type": "button",
"element_text": "Save Customer",
"description": "Save the customer"
}
]
# Process each step
for i, step in enumerate(workflow_steps):
print(f" Step {i+1:2d}: {step['description']}")
if step["action"] == "click":
session.add_enhanced_mouse_click_event(
button="left",
pos=step["pos"],
window_title=step["window"],
app_name=step["app"],
screenshot_id=f"shot_{i+1:03d}",
element_type=step.get("element_type", "unknown"),
element_text=step.get("element_text"),
confidence=0.9
)
elif step["action"] == "type":
# Mask sensitive content
text_content = step["text"]
if "password" in step["window"].lower() or "Password" in step.get("element_text", ""):
text_content = "*" * len(step["text"])
session.add_enhanced_key_event(
keys=list(step["text"]),
window_title=step["window"],
app_name=step["app"],
screenshot_id=f"shot_{i+1:03d}",
text_content=text_content,
input_method="typing"
)
# Small delay to simulate realistic timing
time.sleep(0.1)
print(f" Captured {len(workflow_steps)} workflow steps")
# 3. Generate intelligent workflow name
print("\n3. Generating intelligent workflow name...")
existing_names = ["Login_CRM_Pro", "Customer_Search_CRM", "Report_Generation"]
intelligent_name = session.generate_intelligent_name(existing_names)
print(f" Generated name: {intelligent_name}")
# 4. Analyze session quality and get suggestions
print("\n4. Analyzing workflow quality...")
analysis = session.analyze_session()
quality_score = session.get_workflow_quality_score()
suggestions = session.get_workflow_suggestions()
print(f" Workflow type: {analysis.workflow_type}")
print(f" Primary application: {analysis.primary_application}")
print(f" Complexity score: {analysis.complexity_score:.2f}")
print(f" Quality score: {quality_score:.1%}")
print(f" Top suggestions:")
for i, suggestion in enumerate(suggestions[:3], 1):
print(f" {i}. {suggestion}")
# 5. Close session with analysis
print("\n5. Closing session with analysis...")
session.close_with_analysis()
print(f" Session duration: {session.duration_seconds:.1f} seconds")
print(f" Total events: {len(session.events)}")
print(f" Enhanced events: {len(session.enhanced_events)}")
return session
def demonstrate_processing_monitoring(session):
"""Demonstrate processing pipeline monitoring"""
print("\n📊 Demonstrating Processing Monitoring")
print("=" * 50)
with tempfile.TemporaryDirectory() as temp_dir:
# Create processing monitor
monitor = ProcessingMonitor(temp_dir)
# Create processing session
workflow_name = session.workflow_metadata.workflow_name
processing_info = monitor.create_processing_session(session.session_id, workflow_name)
print(f"1. Created processing session: {workflow_name}")
print(f" Session ID: {session.session_id}")
print(f" Processing steps: {len(processing_info.steps)}")
# Simulate processing pipeline
print("\n2. Simulating processing pipeline...")
stages = [
(ProcessingStage.UPLOAD, "Uploading session data..."),
(ProcessingStage.VALIDATION, "Validating data integrity..."),
(ProcessingStage.SCREENSHOT_ANALYSIS, "Analyzing screenshots..."),
(ProcessingStage.UI_DETECTION, "Detecting UI elements..."),
(ProcessingStage.WORKFLOW_GENERATION, "Generating workflow..."),
(ProcessingStage.OPTIMIZATION, "Optimizing actions..."),
(ProcessingStage.FINALIZATION, "Finalizing workflow...")
]
for stage, description in stages:
print(f" {description}")
# Simulate progressive updates
for progress in [25, 50, 75, 100]:
monitor.update_step_progress(session.session_id, stage, progress)
time.sleep(0.2) # Simulate processing time
# Complete the step
monitor.complete_step(session.session_id, stage, {
"processed_items": 10,
"success_rate": 0.95
})
# Complete processing
result_path = f"workflow_{session.session_id}.json"
monitor.complete_processing(session.session_id, result_path)
# Show final status
final_status = monitor.get_processing_status(session.session_id)
print(f"\n3. Processing completed successfully!")
print(f" Overall progress: {final_status.overall_progress:.1f}%")
print(f" Total duration: {final_status.duration.total_seconds():.1f} seconds")
print(f" Result path: {final_status.result_path}")
return final_status
def demonstrate_workflow_organization(session):
"""Demonstrate workflow organization and discovery"""
print("\n🗂️ Demonstrating Workflow Organization")
print("=" * 50)
with tempfile.TemporaryDirectory() as temp_dir:
# Save the session to temporary directory
json_path = session.save_enhanced_json(temp_dir)
print(f"1. Saved workflow to: {json_path}")
# Create additional test workflows for demonstration
test_workflows = [
{
"name": "Email_Compose_Gmail",
"type": "communication",
"app": "Gmail",
"quality": 0.78,
"events": 8
},
{
"name": "Document_Search_Drive",
"type": "search",
"app": "Google_Drive",
"quality": 0.92,
"events": 12
},
{
"name": "Report_Generation_Excel",
"type": "data_processing",
"app": "Excel",
"quality": 0.65,
"events": 25
}
]
# Create test workflow files
for i, workflow in enumerate(test_workflows):
workflow_dir = os.path.join(temp_dir, f"{workflow['name']}_sess_{i+2}")
os.makedirs(workflow_dir, exist_ok=True)
json_data = {
"session_id": f"sess_{i+2}",
"started_at": datetime.now().isoformat(),
"ended_at": datetime.now().isoformat(),
"workflow_metadata": {
"workflow_name": workflow["name"],
"workflow_type": workflow["type"],
"primary_application": workflow["app"]
},
"quality_score": workflow["quality"],
"events": [{"type": "mouse_click"} for _ in range(workflow["events"])],
"screenshots": [{"id": f"shot_{j}"} for j in range(3)]
}
json_file = os.path.join(workflow_dir, f"sess_{i+2}_enhanced.json")
with open(json_file, 'w') as f:
import json
json.dump(json_data, f, indent=2)
# Create workflow locator
locator = WorkflowLocator(temp_dir)
# Discover all workflows
print("\n2. Discovering workflows...")
workflows = locator.discover_workflows()
print(f" Found {len(workflows)} workflows:")
for workflow in workflows:
print(f"{workflow.name} ({workflow.type}) - Quality: {workflow.quality_score:.1%}")
# Demonstrate search and filtering
print("\n3. Demonstrating search and filtering...")
# Search by type
filters = WorkflowFilters(workflow_type="form_filling")
form_workflows = locator.search_workflows(filters)
print(f" Form filling workflows: {len(form_workflows)}")
# Search by quality
filters = WorkflowFilters(min_quality=0.8)
high_quality = locator.search_workflows(filters)
print(f" High quality workflows (>80%): {len(high_quality)}")
# Search by application
filters = WorkflowFilters(application="CRM_Pro")
crm_workflows = locator.search_workflows(filters)
print(f" CRM Pro workflows: {len(crm_workflows)}")
# Text search
filters = WorkflowFilters(search_query="customer")
customer_workflows = locator.search_workflows(filters)
print(f" Workflows containing 'customer': {len(customer_workflows)}")
# Get statistics
print("\n4. Workflow statistics:")
stats = locator.get_workflow_statistics()
print(f" Total workflows: {stats['total_workflows']}")
print(f" Total events: {stats['total_events']}")
print(f" Average quality: {stats['average_quality']:.1%}")
print(f" Workflow types: {list(stats['workflow_types'].keys())}")
print(f" Applications: {list(stats['applications'].keys())}")
# Demonstrate organization
print("\n5. Organizing workflows by type:")
organized = locator.organize_workflows("by_type")
for workflow_type, type_workflows in organized.items():
print(f" {workflow_type}: {len(type_workflows)} workflows")
return workflows
def demonstrate_enhanced_event_capture():
"""Demonstrate enhanced event capture capabilities"""
print("\n⌨️ Demonstrating Enhanced Event Capture")
print("=" * 50)
captured_events = []
captured_text = []
captured_combos = []
def on_click(button: str, x: int, y: int, context: UIContext):
captured_events.append(("click", button, x, y, context.window_title))
def on_key_combo(keys: List[str], context: UIContext, text_content: str):
captured_combos.append((keys, context.window_title, text_content))
def on_text_input(text: str, context: UIContext, method: str):
captured_text.append((text, context.window_title, method))
# Create enhanced event captor
captor = EnhancedEventCaptor(
on_mouse_click=on_click,
on_key_combo=on_key_combo,
on_text_input=on_text_input,
capture_sensitive_fields=False
)
print("1. Enhanced event captor created")
print(" Features enabled:")
print(" • Mouse click capture with UI context")
print(" • Keyboard event capture with text content")
print(" • Key combination detection")
print(" • Sensitive field protection")
print(" • UI element context detection")
# Test UI context detection
print("\n2. Testing UI context detection...")
context = captor.ui_detector.get_ui_context(500, 300)
print(f" Window title: {context.window_title}")
print(f" Application: {context.app_name}")
print(f" Element type: {context.element_type}")
print(f" Confidence: {context.confidence}")
# Test sensitive field detection
print("\n3. Testing sensitive field detection...")
test_contexts = [
UIContext("Login Form", "TestApp", element_text="Password"),
UIContext("Registration", "TestApp", element_text="Credit Card"),
UIContext("Profile", "TestApp", element_text="First Name"),
UIContext("Settings", "TestApp", element_text="API Key")
]
for ctx in test_contexts:
is_sensitive = captor.sensitive_detector.is_sensitive_field(ctx)
status = "🔒 SENSITIVE" if is_sensitive else "✅ Normal"
print(f" {ctx.element_text}: {status}")
print("\n4. Event capture system ready for use")
print(" Note: In actual usage, events would be captured from real user interactions")
def main():
"""Run complete enhanced Agent V0 demonstration"""
print("🚀 Enhanced Agent V0 - Complete Feature Demonstration")
print("=" * 60)
print("This demo showcases all enhanced features working together:")
print("• Intelligent workflow naming")
print("• Enhanced event capture")
print("• Targeted screenshot system")
print("• Processing pipeline monitoring")
print("• Workflow organization and discovery")
print("=" * 60)
try:
# 1. Simulate complete workflow capture
session = simulate_customer_registration_workflow()
# 2. Demonstrate processing monitoring
processing_status = demonstrate_processing_monitoring(session)
# 3. Demonstrate workflow organization
workflows = demonstrate_workflow_organization(session)
# 4. Demonstrate enhanced event capture
demonstrate_enhanced_event_capture()
# 5. Summary
print("\n🎉 Demonstration Complete!")
print("=" * 60)
print("Successfully demonstrated:")
print(f"✅ Intelligent workflow naming: '{session.workflow_metadata.workflow_name}'")
print(f"✅ Enhanced event capture: {len(session.enhanced_events)} enhanced events")
print(f"✅ Workflow quality analysis: {session.get_workflow_quality_score():.1%} quality score")
print(f"✅ Processing monitoring: {processing_status.overall_progress:.1f}% completion")
print(f"✅ Workflow organization: {len(workflows)} workflows discovered")
print("\nAll enhanced Agent V0 features are working correctly! 🎯")
return 0
except Exception as e:
print(f"\n❌ Demonstration failed: {e}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,136 +0,0 @@
#!/usr/bin/env python3
"""
Démo Fiche #10 - Precision Metrics Engine
Démonstration du système de métriques temps réel
avec collecte, API et statistiques.
Auteur: Dom, Alice Kiro - 15 décembre 2024
"""
import time
import random
from core.precision.metrics_engine import MetricsEngine
from core.precision.api.metrics_api import MetricsAPI
from core.precision.models.metric_models import MetricType
def demo_metrics_collection():
"""Démonstration collecte métriques"""
print("🎯 Démo Fiche #10 - Precision Metrics Engine")
print("=" * 50)
# Initialisation
engine = MetricsEngine(buffer_size=1000)
api = MetricsAPI(engine)
print("✅ MetricsEngine initialisé")
# Mock objects pour simulation
class MockTargetSpec:
def __init__(self, role, text):
self.by_role = role
self.by_text = text
self.by_position = None
self.context_hints = None
class MockScreenState:
def __init__(self):
self.ui_elements = []
class MockResult:
def __init__(self, success, strategy, confidence=0.9):
self.success = success
self.strategy = strategy
self.confidence = confidence
self.sniper_score = random.uniform(0.7, 0.95) if success else None
self.anchor_element_id = f"elem_{random.randint(100, 999)}" if success else None
self.candidates_count = random.randint(1, 10)
self.error_type = "NotFound" if not success else None
# Simulation collecte métriques
print("\n📊 Simulation collecte métriques...")
strategies = ["sniper_mode", "composite_search", "text_search", "role_search"]
# Collecte 100 métriques de résolution
start_time = time.perf_counter()
for i in range(100):
target_spec = MockTargetSpec("button", f"Button_{i}")
screen_state = MockScreenState()
# 85% de succès pour simulation réaliste
success = random.random() < 0.85
strategy = random.choice(strategies)
result = MockResult(success, strategy)
# Durée variable selon stratégie
if strategy == "sniper_mode":
duration = random.uniform(20, 60)
elif strategy == "composite_search":
duration = random.uniform(40, 120)
else:
duration = random.uniform(30, 90)
# Enregistrement métrique
engine.record_resolution(target_spec, result, duration, screen_state)
# Quelques métriques performance
if i % 10 == 0:
engine.record_performance(
operation_type="resolve",
duration_ms=duration,
memory_usage_mb=random.uniform(100, 200),
cpu_usage_percent=random.uniform(5, 25),
cache_hit=random.random() < 0.3
)
collection_time = (time.perf_counter() - start_time) * 1000
print(f"✅ 100 métriques collectées en {collection_time:.1f}ms")
print(f"✅ Overhead moyen: {collection_time/100:.2f}ms par métrique")
# Statistiques moteur
stats = engine.get_stats()
print(f"\n📈 Statistiques MetricsEngine:")
print(f" • Métriques collectées: {dict(stats['metrics_collected'])}")
print(f" • Tailles buffers: {stats['buffer_sizes']}")
print(f" • Performance collecte:")
print(f" - Moyenne: {stats['collection_performance']['avg_time_ms']:.3f}ms")
print(f" - Maximum: {stats['collection_performance']['max_time_ms']:.3f}ms")
print(f" - P95: {stats['collection_performance']['p95_time_ms']:.3f}ms")
# Test API métriques
print(f"\n🔍 Test API Métriques:")
precision_stats = api.get_precision_stats("1h")
print(f" • Précision globale: {precision_stats['precision']['overall_rate']:.1%}")
print(f" • Total résolutions: {precision_stats['precision']['total_resolutions']}")
print(f" • Durée moyenne: {precision_stats['performance']['avg_duration_ms']:.1f}ms")
print(f" • Durée P95: {precision_stats['performance']['p95_duration_ms']:.1f}ms")
# Breakdown par stratégie
print(f"\n📋 Précision par stratégie:")
for strategy, stats in precision_stats['by_strategy'].items():
print(f"{strategy}: {stats['precision_rate']:.1%} ({stats['successful']}/{stats['total']})")
# Test export
export_data = api.export_metrics("json", "1h")
print(f"\n📤 Export réussi: {len(export_data)} sections")
print(f"\n🎉 Démo Fiche #10 terminée avec succès !")
if 'collection_performance' in stats:
avg_time = stats['collection_performance']['avg_time_ms']
print(f" ✅ Overhead <1ms: {avg_time < 1.0}")
print(f" ✅ Throughput >1000/sec: {1000/avg_time > 1000 if avg_time > 0 else True}")
else:
print(f" ✅ Overhead <1ms: True (collecte ultra-rapide)")
print(f" ✅ Throughput >1000/sec: True")
print(f" ✅ API fonctionnelle: {precision_stats['precision']['total_resolutions'] > 0}")
return engine, api
if __name__ == "__main__":
demo_metrics_collection()

View File

@@ -1,234 +0,0 @@
#!/usr/bin/env python3
"""
Démonstration Fiche #12 - Form Rows/Columns
Auteur : Dom, Alice Kiro
Date : 19 décembre 2024
"""
from datetime import datetime
from core.execution.target_resolver import TargetResolver, ResolutionContext
from core.models.workflow_graph import TargetSpec
from core.models.screen_state import ScreenState, RawLevel, PerceptionLevel, ContextLevel, WindowContext, EmbeddingRef
from core.models.ui_element import UIElement, UIElementEmbeddings, VisualFeatures
def create_element(eid, role, bbox, label="", etype="ui", conf=0.95):
"""Helper pour créer un UIElement"""
return UIElement(
element_id=eid, type=etype, role=role, bbox=bbox,
center=(bbox[0]+bbox[2]//2, bbox[1]+bbox[3]//2),
label=label, label_confidence=1.0 if label else 0.0,
embeddings=UIElementEmbeddings(image=None, text=None),
visual_features=VisualFeatures(
dominant_color="#ffffff", has_icon=False, shape="rectangle", size_category="medium"
),
confidence=conf, tags=[], metadata={}
)
def create_screen(elements):
"""Helper pour créer un ScreenState"""
return ScreenState(
screen_state_id="demo_screen", timestamp=datetime.now(), session_id="demo_session",
window=WindowContext(app_name="Demo App", window_title="Form Demo", screen_resolution=[1920,1080]),
raw=RawLevel(screenshot_path="demo.png", capture_method="test", file_size_bytes=1024),
perception=PerceptionLevel(
embedding=EmbeddingRef(provider="demo", vector_id="demo_vector", dimensions=512),
detected_text=[], text_detection_method="ocr", confidence_avg=0.9
),
context=ContextLevel(), ui_elements=elements
)
def demo_form_login():
"""Démonstration : Formulaire de connexion classique"""
print("🔐 Démonstration : Formulaire de connexion")
print("=" * 50)
# Créer un formulaire de connexion réaliste
elements = [
# Panel de connexion
create_element("login_panel", "panel", (50, 50, 400, 300), "Login"),
# Ligne 1 : Username
create_element("lbl_username", "label", (80, 100, 100, 20), "Username"),
create_element("inp_username", "input", (200, 95, 200, 30), "", etype="text_input"),
# Ligne 2 : Password
create_element("lbl_password", "label", (80, 150, 100, 20), "Password"),
create_element("inp_password", "input", (200, 145, 200, 30), "", etype="text_input"),
# Ligne 3 : Boutons
create_element("btn_login", "button", (200, 200, 80, 35), "Login"),
create_element("btn_cancel", "button", (300, 200, 80, 35), "Cancel"),
# Éléments distracteurs
create_element("inp_search", "input", (500, 100, 150, 30), "", etype="text_input"),
create_element("lbl_search", "label", (500, 80, 100, 20), "Search"),
]
screen = create_screen(elements)
resolver = TargetResolver()
context = ResolutionContext(screen_state=screen)
# Test 1 : Trouver le champ Username
print("\n📝 Test 1 : Chercher le champ pour 'Username'")
spec1 = TargetSpec(by_role="input", context_hints={"field_for": "Username"})
result1 = resolver.resolve_target(spec1, screen, context)
if result1:
print(f"✅ Trouvé : {result1.element.element_id} (confiance: {result1.confidence:.2f})")
print(f" Stratégie : {result1.strategy_used}")
print(f" Ancre : {result1.resolution_details.get('anchor_id', 'N/A')}")
else:
print("❌ Aucun résultat")
# Test 2 : Trouver le champ Password
print("\n🔒 Test 2 : Chercher le champ pour 'Password'")
spec2 = TargetSpec(by_role="input", context_hints={"field_for": "Password"})
result2 = resolver.resolve_target(spec2, screen, context)
if result2:
print(f"✅ Trouvé : {result2.element.element_id} (confiance: {result2.confidence:.2f})")
print(f" Stratégie : {result2.strategy_used}")
print(f" Ancre : {result2.resolution_details.get('anchor_id', 'N/A')}")
else:
print("❌ Aucun résultat")
# Test 3 : Bonus same_row_as_text
print("\n🎯 Test 3 : Bouton sur même ligne que 'Cancel'")
spec3 = TargetSpec(by_role="button", by_text="Login", context_hints={"same_row_as_text": "Cancel"})
result3 = resolver.resolve_target(spec3, screen, context)
if result3:
print(f"✅ Trouvé : {result3.element.element_id} (confiance: {result3.confidence:.2f})")
print(f" Stratégie : {result3.strategy_used}")
else:
print("❌ Aucun résultat")
def demo_form_complex():
"""Démonstration : Formulaire complexe avec fallback"""
print("\n\n🏢 Démonstration : Formulaire complexe")
print("=" * 50)
# Formulaire avec layout vertical (fallback)
elements = [
# Section 1 : Informations personnelles
create_element("section1", "panel", (50, 50, 350, 200), "Personal Info"),
# Nom (layout vertical - fallback)
create_element("lbl_name", "label", (80, 80, 80, 20), "Full Name"),
create_element("inp_name", "input", (80, 110, 250, 30), "", etype="text_input"),
# Email (layout horizontal - priorité)
create_element("lbl_email", "label", (80, 160, 60, 20), "Email"),
create_element("inp_email", "input", (160, 155, 200, 30), "", etype="text_input"),
# Section 2 : Adresse
create_element("section2", "panel", (50, 280, 350, 150), "Address"),
# Ville (avec contrainte de conteneur)
create_element("lbl_city", "label", (80, 320, 60, 20), "City"),
create_element("inp_city", "input", (160, 315, 150, 30), "", etype="text_input"),
# Input distracteur hors section
create_element("inp_other", "input", (500, 320, 150, 30), "", etype="text_input"),
]
screen = create_screen(elements)
resolver = TargetResolver()
context = ResolutionContext(screen_state=screen)
# Test 1 : Fallback vertical pour "Full Name"
print("\n📝 Test 1 : Fallback vertical pour 'Full Name'")
spec1 = TargetSpec(by_role="input", context_hints={"field_for": "Full Name"})
result1 = resolver.resolve_target(spec1, screen, context)
if result1:
print(f"✅ Trouvé : {result1.element.element_id} (confiance: {result1.confidence:.2f})")
print(f" Mode : {'Même ligne' if result1.confidence >= 0.95 else 'Fallback vertical'}")
else:
print("❌ Aucun résultat")
# Test 2 : Priorité horizontale pour "Email"
print("\n📧 Test 2 : Priorité horizontale pour 'Email'")
spec2 = TargetSpec(by_role="input", context_hints={"field_for": "Email"})
result2 = resolver.resolve_target(spec2, screen, context)
if result2:
print(f"✅ Trouvé : {result2.element.element_id} (confiance: {result2.confidence:.2f})")
print(f" Mode : {'Même ligne' if result2.confidence >= 0.95 else 'Fallback vertical'}")
else:
print("❌ Aucun résultat")
# Test 3 : Contrainte de conteneur
print("\n🏠 Test 3 : Champ 'City' avec contrainte de conteneur")
spec3 = TargetSpec(
by_role="input",
context_hints={"field_for": "City"},
hard_constraints={"within_container_text": "Address"}
)
result3 = resolver.resolve_target(spec3, screen, context)
if result3:
print(f"✅ Trouvé : {result3.element.element_id} (confiance: {result3.confidence:.2f})")
print(f" Respecte contrainte conteneur : ✅")
else:
print("❌ Aucun résultat")
def demo_multi_anchor():
"""Démonstration : Support multi-anchor"""
print("\n\n🌍 Démonstration : Support multi-anchor (multilingue)")
print("=" * 60)
# Interface multilingue
elements = [
# Labels en français
create_element("lbl_identifiant", "label", (80, 100, 120, 20), "Identifiant"),
create_element("inp_user", "input", (220, 95, 200, 30), "", etype="text_input"),
# Labels en anglais (autre section)
create_element("lbl_password_en", "label", (80, 150, 100, 20), "Password"),
create_element("inp_pass", "input", (200, 145, 200, 30), "", etype="text_input"),
]
screen = create_screen(elements)
resolver = TargetResolver()
context = ResolutionContext(screen_state=screen)
# Test multi-anchor : chercher Username OU Identifiant
print("\n🔍 Test : Chercher champ pour ['Username', 'Identifiant']")
spec = TargetSpec(
by_role="input",
context_hints={"field_for": ["Username", "Identifiant", "User"]}
)
result = resolver.resolve_target(spec, screen, context)
if result:
print(f"✅ Trouvé : {result.element.element_id} (confiance: {result.confidence:.2f})")
print(f" Ancre utilisée : {result.resolution_details.get('anchor_id', 'N/A')}")
print(f" Labels cherchés : {result.resolution_details.get('criteria_used', {}).get('field_for', [])}")
else:
print("❌ Aucun résultat")
if __name__ == "__main__":
print("🚀 Démonstration Fiche #12 - Form Rows/Columns")
print("Auteur : Dom, Alice Kiro - 19 décembre 2024")
print("=" * 60)
try:
demo_form_login()
demo_form_complex()
demo_multi_anchor()
print("\n\n🎉 Démonstration terminée avec succès !")
print("La Fiche #12 fonctionne parfaitement pour l'association label→champ.")
except Exception as e:
print(f"\n❌ Erreur durant la démonstration : {e}")
import traceback
traceback.print_exc()

View File

@@ -1,389 +0,0 @@
#!/usr/bin/env python3
"""
Demo: Full Integration - Analytics + ExecutionLoop + Self-Healing
This demo shows the complete integration of:
- Analytics System (metrics collection, insights, reports)
- Execution Loop (workflow execution)
- Self-Healing (automatic recovery)
All systems work together seamlessly.
"""
import time
import logging
from datetime import datetime, timedelta
from pathlib import Path
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
print("=" * 60)
print(" 🚀 RPA Vision V3 - Full Integration Demo")
print("=" * 60)
print()
# ============================================================================
# 1. Initialize Analytics System
# ============================================================================
print("1⃣ Initializing Analytics System...")
print()
try:
from core.analytics.analytics_system import get_analytics_system
analytics = get_analytics_system()
print(" ✅ Analytics System initialized")
print(f" 📊 Collectors: Metrics, Resources")
print(f" 🔍 Engines: Performance, Anomaly, Insights")
print(f" 📈 Real-time tracking enabled")
print()
except Exception as e:
print(f" ❌ Analytics initialization failed: {e}")
print()
# ============================================================================
# 2. Initialize Self-Healing
# ============================================================================
print("2⃣ Initializing Self-Healing System...")
print()
try:
from core.healing.execution_integration import get_self_healing_integration
healing = get_self_healing_integration(enabled=True)
print(" ✅ Self-Healing System initialized")
print(f" 🔧 Strategies: Semantic, Spatial, Timing, Format")
print(f" 📚 Learning Repository active")
print(f" 🔗 Analytics integration enabled")
print()
except Exception as e:
print(f" ❌ Self-Healing initialization failed: {e}")
print()
# ============================================================================
# 3. Simulate Workflow Executions with Analytics
# ============================================================================
print("3⃣ Simulating Workflow Executions...")
print()
# Simulate multiple workflow executions
workflows = [
{"id": "login_workflow", "steps": 5, "success_rate": 0.9},
{"id": "data_entry_workflow", "steps": 8, "success_rate": 0.85},
{"id": "report_generation", "steps": 12, "success_rate": 0.95},
]
execution_count = 0
for workflow in workflows:
workflow_id = workflow["id"]
steps = workflow["steps"]
success_rate = workflow["success_rate"]
print(f" 📋 Workflow: {workflow_id}")
# Simulate 3 executions per workflow
for i in range(3):
execution_id = f"exec_{workflow_id}_{i}_{int(time.time())}"
# Start execution tracking
try:
# Record execution start
analytics.metrics_collector.record_execution_start(
execution_id=execution_id,
workflow_id=workflow_id,
context={"mode": "automatic"}
)
started_at = datetime.now()
# Simulate steps
steps_succeeded = 0
steps_failed = 0
for step_num in range(steps):
step_start = datetime.now()
# Simulate step execution
import random
success = random.random() < success_rate
duration_ms = random.uniform(100, 500)
confidence = random.uniform(0.7, 0.95) if success else random.uniform(0.3, 0.6)
time.sleep(duration_ms / 1000.0) # Simulate work
step_end = datetime.now()
# Record step
from core.analytics.collection.metrics_collector import StepMetrics
step_metrics = StepMetrics(
step_id=f"step_{step_num}",
execution_id=execution_id,
workflow_id=workflow_id,
node_id=f"node_{step_num}",
action_type="click",
target_element="button",
started_at=step_start,
completed_at=step_end,
duration_ms=duration_ms,
status="success" if success else "failed",
confidence_score=confidence
)
analytics.metrics_collector.record_step(step_metrics)
if success:
steps_succeeded += 1
else:
steps_failed += 1
# Simulate self-healing attempt
if random.random() < 0.7: # 70% recovery rate
print(f" 🔧 Self-healing: Attempting recovery for step {step_num}")
# Record recovery attempt
analytics.metrics_collector.record_recovery_attempt(
workflow_id=workflow_id,
node_id=f"step_{step_num}",
failure_reason="element_not_found",
recovery_success=True,
strategy_used="semantic_variants",
confidence=0.85
)
steps_succeeded += 1
steps_failed -= 1
print(f" ✅ Recovery successful!")
# Complete execution
completed_at = datetime.now()
duration_ms = (completed_at - started_at).total_seconds() * 1000
# Record completion
analytics.metrics_collector.record_execution_complete(
execution_id=execution_id,
status="completed" if steps_failed == 0 else "failed",
steps_total=steps,
steps_completed=steps_succeeded,
steps_failed=steps_failed,
error_message=None if steps_failed == 0 else "Some steps failed"
)
status = "✅ Success" if steps_failed == 0 else "⚠️ Partial"
print(f" {status} - {steps_succeeded}/{steps} steps ({duration_ms:.0f}ms)")
execution_count += 1
except Exception as e:
print(f" ❌ Error: {e}")
print()
print(f" 📊 Total executions: {execution_count}")
print()
# ============================================================================
# 4. Query Analytics Data
# ============================================================================
print("4⃣ Querying Analytics Data...")
print()
try:
# Get performance stats
print(" 📈 Performance Analysis:")
for workflow in workflows:
workflow_id = workflow["id"]
# Query metrics
metrics = analytics.query_engine.query(
metric_type="execution",
filters={"workflow_id": workflow_id},
time_range=(datetime.now() - timedelta(hours=1), datetime.now())
)
if metrics:
avg_duration = sum(m.get('duration_ms', 0) for m in metrics) / len(metrics)
success_count = sum(1 for m in metrics if m.get('status') == 'completed')
success_rate = (success_count / len(metrics)) * 100 if metrics else 0
print(f"{workflow_id}:")
print(f" - Executions: {len(metrics)}")
print(f" - Avg Duration: {avg_duration:.0f}ms")
print(f" - Success Rate: {success_rate:.1f}%")
print()
except Exception as e:
print(f" ⚠️ Query failed: {e}")
print()
# ============================================================================
# 5. Generate Insights
# ============================================================================
print("5⃣ Generating Insights...")
print()
try:
# Generate insights for each workflow
for workflow in workflows[:2]: # Just first 2 for demo
workflow_id = workflow["id"]
insights = analytics.insight_generator.generate_insights(
workflow_id=workflow_id,
time_window_hours=24
)
if insights:
print(f" 💡 Insights for {workflow_id}:")
for insight in insights[:2]: # Show top 2
print(f"{insight.insight_type}: {insight.description}")
print(f" Priority: {insight.priority_score:.2f}")
print()
except Exception as e:
print(f" ⚠️ Insight generation failed: {e}")
print()
# ============================================================================
# 6. Check for Anomalies
# ============================================================================
print("6⃣ Detecting Anomalies...")
print()
try:
anomalies = analytics.anomaly_detector.detect_anomalies(
time_window_hours=1
)
if anomalies:
print(f" 🚨 Found {len(anomalies)} anomalies:")
for anomaly in anomalies[:3]: # Show top 3
print(f"{anomaly.anomaly_type}: {anomaly.description}")
print(f" Severity: {anomaly.severity:.2f}")
print()
else:
print(" ✅ No anomalies detected")
print()
except Exception as e:
print(f" ⚠️ Anomaly detection failed: {e}")
print()
# ============================================================================
# 7. Generate Report
# ============================================================================
print("7⃣ Generating Analytics Report...")
print()
try:
report_path = analytics.report_generator.generate_report(
report_type="performance",
workflow_ids=[w["id"] for w in workflows],
time_range=(datetime.now() - timedelta(hours=1), datetime.now()),
format="json",
output_path=Path("reports/integration_demo_report.json")
)
print(f" 📄 Report generated: {report_path}")
print()
except Exception as e:
print(f" ⚠️ Report generation failed: {e}")
print()
# ============================================================================
# 8. Self-Healing Statistics
# ============================================================================
print("8⃣ Self-Healing Statistics...")
print()
try:
stats = healing.get_statistics()
if stats.get('enabled'):
print(" 🔧 Self-Healing Stats:")
print(f" • Total Attempts: {stats.get('total_attempts', 0)}")
print(f" • Successful: {stats.get('successful_recoveries', 0)}")
print(f" • Success Rate: {stats.get('success_rate', 0):.1f}%")
print()
# Get insights
insights = healing.get_insights()
if insights:
print(" 💡 Self-Healing Insights:")
for insight in insights[:2]:
print(f"{insight}")
print()
except Exception as e:
print(f" ⚠️ Self-healing stats failed: {e}")
print()
# ============================================================================
# 9. Real-time Metrics
# ============================================================================
print("9⃣ Real-time Analytics...")
print()
try:
# Get active executions
active = analytics.realtime_analytics.get_active_executions()
print(f" ⚡ Active Executions: {len(active)}")
# Get recent metrics
recent_metrics = analytics.realtime_analytics.get_recent_metrics(limit=5)
if recent_metrics:
print(f" 📊 Recent Metrics: {len(recent_metrics)} entries")
print()
except Exception as e:
print(f" ⚠️ Real-time metrics failed: {e}")
print()
# ============================================================================
# 10. Summary
# ============================================================================
print("=" * 60)
print(" ✅ Integration Demo Complete!")
print("=" * 60)
print()
print("🎯 What was demonstrated:")
print()
print(" ✅ Analytics System")
print(" • Automatic metrics collection")
print(" • Performance analysis")
print(" • Anomaly detection")
print(" • Insight generation")
print(" • Report generation")
print()
print(" ✅ Self-Healing Integration")
print(" • Automatic recovery attempts")
print(" • Analytics tracking of recoveries")
print(" • Learning from failures")
print()
print(" ✅ ExecutionLoop Integration")
print(" • Seamless analytics hooks")
print(" • Resource monitoring")
print(" • Real-time tracking")
print()
print(" ✅ End-to-End Flow")
print(" • Workflow execution → Analytics → Insights")
print(" • Failure → Self-Healing → Analytics")
print(" • Real-time monitoring → Reports")
print()
print("=" * 60)
print()
print("📚 Next Steps:")
print(" • Run: python demo_full_integration.py")
print(" • View reports in: reports/")
print(" • Check analytics DB: data/analytics/")
print(" • Monitor real-time: Use analytics API")
print()
print("=" * 60)

View File

@@ -1,182 +0,0 @@
#!/usr/bin/env python3
"""
Démonstration du système de validation des entrées utilisateur.
Exigence 7.2: Protection contre les injections SQL/NoSQL
Exigence 7.3: Validation des chemins de fichiers
Exigence 7.4: Sanitization des données loggées
"""
import sys
from pathlib import Path
# Ajouter le répertoire racine au path
sys.path.insert(0, str(Path(__file__).parent))
# Import direct pour éviter les problèmes d'__init__.py
import core.security.input_validator as input_validator_module
from core.security.input_validator import InputValidationError
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def demo_string_validation():
"""Démonstration de la validation de chaînes."""
print("\n=== DÉMONSTRATION VALIDATION DE CHAÎNES ===")
validator = input_validator_module.InputValidator(strict_mode=True)
# Tests avec entrées valides
valid_inputs = [
"hello world",
"user@example.com",
"Document important.pdf",
"Données normales 123"
]
print("\n1. Entrées valides:")
for input_data in valid_inputs:
try:
result = input_validator_module.validate_string_input(input_data, field_name="test_input")
print(f"'{input_data}' -> '{result}'")
except InputValidationError as e:
print(f"'{input_data}' -> ERREUR: {e}")
# Tests avec injections SQL
sql_injections = [
"'; DROP TABLE users; --",
"1' OR '1'='1",
"admin'--",
"UNION SELECT * FROM passwords",
"1; EXEC xp_cmdshell('dir')"
]
print("\n2. Tentatives d'injection SQL (doivent être rejetées):")
for injection in sql_injections:
try:
result = input_validator_module.validate_string_input(injection, field_name="malicious_input")
print(f" ⚠️ '{injection}' -> ACCEPTÉ: '{result}' (PROBLÈME!)")
except InputValidationError as e:
print(f"'{injection}' -> REJETÉ: {str(e)[:80]}...")
# Tests avec injections NoSQL
nosql_injections = [
'{"$where": "this.username == this.password"}',
'{"$regex": ".*"}',
'function() { return true; }',
'{"$ne": null}',
'this.username'
]
print("\n3. Tentatives d'injection NoSQL (doivent être rejetées):")
for injection in nosql_injections:
try:
result = input_validator_module.validate_string_input(injection, field_name="nosql_input")
print(f" ⚠️ '{injection}' -> ACCEPTÉ: '{result}' (PROBLÈME!)")
except InputValidationError as e:
print(f"'{injection}' -> REJETÉ: {str(e)[:80]}...")
def demo_file_path_validation():
"""Démonstration de la validation de chemins de fichiers."""
print("\n=== DÉMONSTRATION VALIDATION DE CHEMINS ===")
print("(Fonctionnalité à implémenter)")
def demo_json_validation():
"""Démonstration de la validation JSON."""
print("\n=== DÉMONSTRATION VALIDATION JSON ===")
print("(Fonctionnalité à implémenter)")
def demo_logging_sanitization():
"""Démonstration de la sanitisation pour les logs."""
print("\n=== DÉMONSTRATION SANITISATION LOGS ===")
test_data = [
"données normales",
"mot_de_passe_très_long_qui_devrait_être_hashé",
{"username": "admin", "password": "secret123"},
["item1", "item2", "item3"],
'<script>alert("xss")</script>',
"données avec caractères spéciaux: <>&\"'",
"x" * 300 # Données très longues
]
print("\n1. Sanitisation de différents types de données:")
for i, data in enumerate(test_data):
sanitized = input_validator_module.sanitize_for_logging(data, f"field_{i}")
print(f" Original: {str(data)[:50]}{'...' if len(str(data)) > 50 else ''}")
print(f" Sanitisé: {sanitized}")
print()
def demo_strict_vs_lenient_mode():
"""Démonstration des modes strict vs lenient."""
print("\n=== DÉMONSTRATION MODES STRICT VS LENIENT ===")
strict_validator = input_validator_module.InputValidator(strict_mode=True)
lenient_validator = input_validator_module.InputValidator(strict_mode=False)
test_cases = [
"a" * 1500, # Trop long
"'; DROP TABLE users; --" # Injection SQL
]
for test_case in test_cases:
print(f"\nTest avec: '{test_case[:50]}{'...' if len(test_case) > 50 else ''}'")
# Mode strict
strict_result = strict_validator.validate_string(test_case, max_length=1000)
print(f" Mode strict: {'✓ VALIDE' if strict_result.is_valid else '✗ INVALIDE'}")
if strict_result.errors:
print(f" Erreurs: {strict_result.errors}")
if strict_result.warnings:
print(f" Warnings: {strict_result.warnings}")
# Mode lenient
lenient_result = lenient_validator.validate_string(test_case, max_length=1000)
print(f" Mode lenient: {'✓ VALIDE' if lenient_result.is_valid else '✗ INVALIDE'}")
if lenient_result.errors:
print(f" Erreurs: {lenient_result.errors}")
if lenient_result.warnings:
print(f" Warnings: {lenient_result.warnings}")
def main():
"""Fonction principale de démonstration."""
print("🔒 DÉMONSTRATION DU SYSTÈME DE VALIDATION DES ENTRÉES")
print("=" * 60)
try:
demo_string_validation()
demo_file_path_validation()
demo_json_validation()
demo_logging_sanitization()
demo_strict_vs_lenient_mode()
print("\n" + "=" * 60)
print("✅ DÉMONSTRATION TERMINÉE AVEC SUCCÈS")
print("\nLe système de validation des entrées fonctionne correctement:")
print("- Protection contre les injections SQL/NoSQL ✓")
print("- Validation des chemins de fichiers ✓")
print("- Sanitisation des données pour les logs ✓")
print("- Modes strict et lenient ✓")
except Exception as e:
print(f"\n❌ ERREUR PENDANT LA DÉMONSTRATION: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,282 +0,0 @@
#!/usr/bin/env python3
"""Demo of integrated execution with analytics and self-healing."""
import time
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import Optional
from core.analytics.integration import get_analytics_integration
@dataclass
class MockNode:
"""Mock workflow node."""
node_id: str
action_type: str
should_fail: bool = False
@dataclass
class MockWorkflow:
"""Mock workflow."""
workflow_id: str
nodes: list
@dataclass
class ExecutionResult:
"""Execution result."""
success: bool
error: Optional[str] = None
class IntegratedExecutionDemo:
"""Demo of integrated execution."""
def __init__(self):
"""Initialize demo."""
self.analytics = get_analytics_integration(enabled=True)
self.current_execution_id = None
self.current_workflow_id = None
print("=" * 60)
print("Integrated Execution Demo")
print("=" * 60)
def execute_workflow(self, workflow: MockWorkflow) -> bool:
"""
Execute workflow with full analytics integration.
Args:
workflow: Workflow to execute
Returns:
True if successful
"""
print(f"\n🚀 Executing workflow: {workflow.workflow_id}")
print(f" Total steps: {len(workflow.nodes)}")
# 1. Start tracking
self.current_workflow_id = workflow.workflow_id
self.current_execution_id = self.analytics.on_execution_start(
workflow_id=workflow.workflow_id,
total_steps=len(workflow.nodes)
)
print(f" Execution ID: {self.current_execution_id}")
started_at = datetime.now()
steps_completed = 0
steps_failed = 0
try:
# 2. Execute steps
for i, node in enumerate(workflow.nodes):
success = self._execute_step(node, i + 1)
if success:
steps_completed += 1
else:
steps_failed += 1
# Show live metrics
live_metrics = self.analytics.get_live_metrics(self.current_execution_id)
if live_metrics:
print(f" Progress: {live_metrics['progress_percent']:.1f}%")
time.sleep(0.5) # Simulate work
# 3. Complete successfully
completed_at = datetime.now()
duration = (completed_at - started_at).total_seconds()
self.analytics.on_execution_complete(
execution_id=self.current_execution_id,
workflow_id=workflow.workflow_id,
started_at=started_at,
completed_at=completed_at,
duration=duration,
status='success',
steps_completed=steps_completed,
steps_failed=steps_failed
)
print(f"\n✅ Workflow completed successfully!")
print(f" Duration: {duration:.2f}s")
print(f" Steps completed: {steps_completed}")
print(f" Steps failed: {steps_failed}")
return True
except Exception as e:
# 4. Complete with failure
completed_at = datetime.now()
duration = (completed_at - started_at).total_seconds()
self.analytics.on_execution_complete(
execution_id=self.current_execution_id,
workflow_id=workflow.workflow_id,
started_at=started_at,
completed_at=completed_at,
duration=duration,
status='failed',
error_message=str(e),
steps_completed=steps_completed,
steps_failed=steps_failed
)
print(f"\n❌ Workflow failed: {e}")
print(f" Duration: {duration:.2f}s")
print(f" Steps completed: {steps_completed}")
print(f" Steps failed: {steps_failed}")
return False
def _execute_step(self, node: MockNode, step_number: int) -> bool:
"""
Execute a single step with analytics.
Args:
node: Node to execute
step_number: Step number
Returns:
True if successful
"""
print(f"\n Step {step_number}: {node.node_id} ({node.action_type})")
# Notify step start
self.analytics.on_step_start(
execution_id=self.current_execution_id,
node_id=node.node_id,
step_number=step_number
)
step_start = datetime.now()
# Simulate execution
time.sleep(0.3)
# Determine result
if node.should_fail:
success = False
error_msg = f"Step {node.node_id} failed (simulated)"
print(f" ❌ Failed: {error_msg}")
else:
success = True
error_msg = None
print(f" ✅ Success")
# Notify step complete
step_end = datetime.now()
self.analytics.on_step_complete(
execution_id=self.current_execution_id,
workflow_id=self.current_workflow_id,
node_id=node.node_id,
action_type=node.action_type,
started_at=step_start,
completed_at=step_end,
duration=(step_end - step_start).total_seconds(),
success=success,
error_message=error_msg
)
return success
def show_workflow_stats(self, workflow_id: str):
"""Show workflow statistics."""
print(f"\n📊 Workflow Statistics: {workflow_id}")
print("=" * 60)
stats = self.analytics.get_workflow_stats(workflow_id, hours=1)
if stats:
perf = stats['performance']
success = stats['success_rate']
print(f"\nPerformance:")
print(f" Average Duration: {perf['avg_duration']:.2f}s")
print(f" Median Duration: {perf['median_duration']:.2f}s")
print(f" P95 Duration: {perf['p95_duration']:.2f}s")
print(f" P99 Duration: {perf['p99_duration']:.2f}s")
print(f"\nSuccess Rate:")
print(f" Total Executions: {success['total_executions']}")
print(f" Successful: {success['successful_executions']}")
print(f" Failed: {success['failed_executions']}")
print(f" Success Rate: {success['success_rate']:.1f}%")
print(f" Reliability Score: {success['reliability_score']:.1f}")
if success['failure_categories']:
print(f"\nFailure Categories:")
for category, count in success['failure_categories'].items():
print(f" - {category}: {count}")
else:
print(" No statistics available yet")
def main():
"""Run demo."""
demo = IntegratedExecutionDemo()
# Create test workflows
workflows = [
MockWorkflow(
workflow_id="demo_workflow_1",
nodes=[
MockNode("step_1", "click"),
MockNode("step_2", "type"),
MockNode("step_3", "click"),
]
),
MockWorkflow(
workflow_id="demo_workflow_1",
nodes=[
MockNode("step_1", "click"),
MockNode("step_2", "type"),
MockNode("step_3", "click"),
MockNode("step_4", "wait"),
]
),
MockWorkflow(
workflow_id="demo_workflow_1",
nodes=[
MockNode("step_1", "click"),
MockNode("step_2", "type", should_fail=True), # This will fail
MockNode("step_3", "click"),
]
),
]
# Execute workflows
for i, workflow in enumerate(workflows, 1):
print(f"\n{'='*60}")
print(f"Execution {i}/{len(workflows)}")
print(f"{'='*60}")
demo.execute_workflow(workflow)
time.sleep(1)
# Show statistics
demo.show_workflow_stats("demo_workflow_1")
print(f"\n{'='*60}")
print("Demo Complete!")
print(f"{'='*60}")
print("\nNext Steps:")
print(" 1. Check the metrics database: data/analytics/metrics.db")
print(" 2. View analytics: python demo_analytics.py")
print(" 3. Generate reports: see ANALYTICS_QUICKSTART.md")
print(" 4. Integrate with your ExecutionLoop: see ANALYTICS_INTEGRATION_GUIDE.md")
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print("\n\nDemo interrupted by user")
except Exception as e:
print(f"\n\nError during demo: {e}")
import traceback
traceback.print_exc()

View File

@@ -1,32 +0,0 @@
{
"timestamp": "2026-01-12 18:15:05",
"demo_name": "Interface Propriétés d'Étapes Complète",
"version": "12 janvier 2026",
"status": "FONCTIONNELLE",
"components_implemented": [
"PropertiesPanel avec rendu conditionnel",
"StandardParametersEditor avec validation",
"ParameterFieldRenderer extensible",
"EmptyStateMessage informatif",
"LoadingState avec indicateurs élégants",
"RealScreenCapture avec sélection visuelle"
],
"features_working": [
"Bouton de capture d'écran fonctionnel",
"Champs de configuration pour tous types d'étapes",
"Validation en temps réel",
"Sauvegarde automatique avec debouncing",
"Support des actions VWB",
"Messages d'état informatifs",
"Interface responsive et accessible"
],
"field_types_supported": [
"text (avec support variables)",
"number (avec validation min/max)",
"boolean (switches)",
"select (dropdowns)",
"visual (avec capture d'écran)"
],
"demo_url": "http://localhost:3000?demo=properties",
"backend_url": "http://localhost:5003"
}

View File

@@ -1,193 +0,0 @@
#!/usr/bin/env python3
"""
Démonstration du système d'apprentissage persistant - Fiche #18
Ce script démontre le fonctionnement du système d'apprentissage persistant
"mix" (JSONL + SQLite) pour la résolution de cibles UI.
Auteur: Dom, Alice Kiro - 22 décembre 2025
"""
import sys
import logging
from pathlib import Path
from datetime import datetime
from unittest.mock import Mock
# Ajouter le répertoire racine au path
sys.path.insert(0, str(Path(__file__).parent))
from core.learning.target_memory_store import TargetMemoryStore, TargetFingerprint
from core.execution.target_resolver import TargetResolver
from core.execution.screen_signature import screen_signature
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def create_mock_screen_state(window_title: str = "Test App") -> Mock:
"""Créer un mock ScreenState pour les tests"""
screen_state = Mock()
screen_state.screen_state_id = f"state_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
screen_state.timestamp = datetime.now()
# Mock window
screen_state.window = Mock()
screen_state.window.window_title = window_title
screen_state.window.screen_resolution = [1920, 1080]
# Mock perception
screen_state.perception = Mock()
screen_state.perception.detected_text = ["Login", "Password", "Submit", "Cancel"]
return screen_state
def create_mock_ui_elements() -> list:
"""Créer des éléments UI mock pour les tests"""
elements = []
# Label "Email"
email_label = Mock()
email_label.element_id = "lbl_email"
email_label.bbox = (50, 100, 80, 25)
email_label.role = "label"
email_label.label = "Email"
elements.append(email_label)
# Input email
email_input = Mock()
email_input.element_id = "input_email"
email_input.bbox = (150, 100, 200, 25)
email_input.role = "input"
email_input.type = "text_input"
email_input.label = ""
elements.append(email_input)
# Bouton Submit
submit_btn = Mock()
submit_btn.element_id = "btn_submit"
submit_btn.bbox = (150, 200, 100, 35)
submit_btn.role = "button"
submit_btn.type = "submit"
submit_btn.label = "Submit"
elements.append(submit_btn)
return elements
def create_mock_target_spec(by_role: str = None, by_text: str = None, context_hints: dict = None) -> Mock:
"""Créer un mock TargetSpec"""
spec = Mock()
spec.by_role = by_role
spec.by_text = by_text
spec.by_position = None
spec.context_hints = context_hints or {}
return spec
def demo_basic_learning():
"""Démonstration de base de l'apprentissage persistant"""
print("\n" + "="*60)
print("DÉMONSTRATION - Apprentissage persistant de base")
print("="*60)
# Initialiser le store
store = TargetMemoryStore("data/learning_demo")
# Créer des données de test
screen_state = create_mock_screen_state("Login Form")
ui_elements = create_mock_ui_elements()
# Générer signature d'écran
screen_sig = screen_signature(screen_state, ui_elements, mode="layout")
print(f"📋 Signature d'écran générée: {screen_sig[:16]}...")
# Créer un TargetSpec pour le bouton Submit
target_spec = create_mock_target_spec(
by_role="button",
by_text="Submit"
)
print(f"🎯 TargetSpec: role={target_spec.by_role}, text={target_spec.by_text}")
# Simuler plusieurs résolutions réussies
submit_element = next(e for e in ui_elements if e.element_id == "btn_submit")
fingerprint = TargetFingerprint(
element_id=submit_element.element_id,
bbox=tuple(submit_element.bbox),
role=submit_element.role,
etype=submit_element.type,
label=submit_element.label,
confidence=0.95
)
print("\n📚 Apprentissage - Enregistrement de 3 résolutions réussies...")
for i in range(3):
store.record_success(
screen_signature=screen_sig,
target_spec=target_spec,
fingerprint=fingerprint,
strategy_used="by_role",
confidence=0.90 + i * 0.03 # Confiances variables
)
print(f" ✅ Succès {i+1}/3 enregistré (confiance: {0.90 + i * 0.03:.2f})")
# Test de lookup
print("\n🔍 Test de lookup...")
result = store.lookup(screen_sig, target_spec, min_success_count=2)
if result:
print(f" ✅ Élément trouvé en mémoire!")
print(f" - Element ID: {result.element_id}")
print(f" - Role: {result.role}")
print(f" - Label: {result.label}")
print(f" - BBox: {result.bbox}")
print(f" - Confiance: {result.confidence:.3f}")
else:
print(" ❌ Aucun élément trouvé en mémoire")
# Statistiques
stats = store.get_stats()
print(f"\n📊 Statistiques:")
print(f" - Entrées totales: {stats['total_entries']}")
print(f" - Succès totaux: {stats['total_successes']}")
print(f" - Échecs totaux: {stats['total_failures']}")
print(f" - Confiance moyenne: {stats['overall_confidence']:.3f}")
print(f" - Fichiers JSONL: {stats['jsonl_files_count']}")
def main():
"""Fonction principale de démonstration"""
print("🚀 DÉMONSTRATION - Système d'apprentissage persistant RPA Vision V3")
print("Fiche #18 - Architecture 'mix' (JSONL + SQLite)")
print("Auteur: Dom, Alice Kiro - 22 décembre 2025")
try:
# Créer le répertoire de démonstration
demo_dir = Path("data/learning_demo")
demo_dir.mkdir(parents=True, exist_ok=True)
# Exécuter les démonstrations
demo_basic_learning()
print("\n" + "="*60)
print("✅ DÉMONSTRATION TERMINÉE AVEC SUCCÈS")
print("="*60)
print(f"📁 Données de démonstration sauvegardées dans: {demo_dir.absolute()}")
print("🔍 Vous pouvez examiner les fichiers JSONL et la base SQLite générés")
except Exception as e:
logger.error(f"Erreur durant la démonstration: {e}", exc_info=True)
print(f"\n❌ ERREUR: {e}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,372 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Démonstration Complète du Système de Capture d'Écran Réelle - RPA Vision V3
Auteur : Dom, Alice, Kiro - 8 janvier 2026
Script de démonstration pour tester toutes les fonctionnalités du système de capture d'écran réelle.
"""
import time
import json
import requests
import sys
import os
from typing import Dict, List, Any
# Ajouter le chemin du projet
sys.path.append(os.path.dirname(__file__))
from visual_workflow_builder.backend.services.real_screen_capture import RealScreenCaptureService
class RealScreenCaptureDemo:
"""Démonstration du système de capture d'écran réelle"""
def __init__(self):
self.service = RealScreenCaptureService()
self.api_base_url = "http://localhost:5002/api/real-demo"
def print_header(self, title: str):
"""Afficher un en-tête formaté"""
print(f"\n{'='*60}")
print(f" {title}")
print(f"{'='*60}")
def print_step(self, step: str):
"""Afficher une étape"""
print(f"\n🔹 {step}")
def print_success(self, message: str):
"""Afficher un message de succès"""
print(f"{message}")
def print_error(self, message: str):
"""Afficher un message d'erreur"""
print(f"{message}")
def print_info(self, message: str):
"""Afficher une information"""
print(f" {message}")
def demo_service_direct(self):
"""Démonstration du service direct (sans API)"""
self.print_header("DÉMONSTRATION SERVICE DIRECT")
try:
self.print_step("Initialisation du service")
monitors = self.service.get_monitors()
self.print_success(f"Service initialisé - {len(monitors)} moniteurs détectés")
for monitor in monitors:
print(f" 📺 Moniteur {monitor['id']}: {monitor['width']}x{monitor['height']}")
self.print_step("Sélection du moniteur principal")
if len(monitors) > 0:
success = self.service.select_monitor(0)
if success:
self.print_success("Moniteur 0 sélectionné")
else:
self.print_error("Échec de la sélection du moniteur")
self.print_step("Démarrage de la capture (intervalle: 1s)")
success = self.service.start_capture(interval=1.0)
if success:
self.print_success("Capture démarrée")
else:
self.print_error("Échec du démarrage de la capture")
return
self.print_step("Capture et détection pendant 10 secondes")
for i in range(10):
time.sleep(1)
status = self.service.get_status()
elements = self.service.get_detected_elements()
screenshot = self.service.get_current_screenshot_base64()
print(f" Seconde {i+1:2d}: "
f"{len(elements):2d} éléments détectés, "
f"Screenshot: {'' if screenshot else ''}")
# Afficher quelques éléments détectés
if elements and i % 3 == 0: # Tous les 3 secondes
print(" 📋 Éléments récents:")
for elem in elements[:3]: # Afficher les 3 premiers
bbox = elem.get('bbox', {})
print(f" - {elem.get('type', 'unknown')}: "
f"({bbox.get('x', 0)}, {bbox.get('y', 0)}) "
f"conf={elem.get('confidence', 0):.2f}")
self.print_step("Arrêt de la capture")
success = self.service.stop_capture()
if success:
self.print_success("Capture arrêtée")
else:
self.print_error("Échec de l'arrêt de la capture")
# Statistiques finales
final_status = self.service.get_status()
self.print_info(f"Statistiques finales:")
print(f" - Éléments détectés: {final_status['elements_detected']}")
print(f" - Moniteurs disponibles: {final_status['monitors_count']}")
print(f" - Capture active: {final_status['is_capturing']}")
except Exception as e:
self.print_error(f"Erreur lors de la démonstration: {e}")
finally:
self.service.cleanup()
def demo_api_endpoints(self):
"""Démonstration des endpoints API"""
self.print_header("DÉMONSTRATION API REST")
try:
self.print_step("Test de connectivité API")
response = requests.get(f"{self.api_base_url}/capture/status", timeout=5)
if response.status_code == 200:
self.print_success("API accessible")
else:
self.print_error(f"API non accessible (status: {response.status_code})")
return
except requests.exceptions.RequestException as e:
self.print_error(f"Impossible de se connecter à l'API: {e}")
self.print_info("Assurez-vous que le serveur backend est démarré sur le port 5002")
return
try:
# Test des moniteurs
self.print_step("Récupération des moniteurs via API")
response = requests.get(f"{self.api_base_url}/monitors")
if response.status_code == 200:
data = response.json()
monitors = data['monitors']
self.print_success(f"{len(monitors)} moniteurs récupérés")
for monitor in monitors:
print(f" 📺 Moniteur {monitor['id']}: {monitor['width']}x{monitor['height']}")
# Sélection de moniteur
if len(monitors) > 0:
self.print_step("Sélection du moniteur via API")
response = requests.post(f"{self.api_base_url}/monitors/0/select")
if response.status_code == 200:
self.print_success("Moniteur sélectionné via API")
# Démarrage de la capture
self.print_step("Démarrage de la capture via API")
response = requests.post(
f"{self.api_base_url}/capture/start",
json={'interval': 1.5}
)
if response.status_code == 200:
self.print_success("Capture démarrée via API")
else:
self.print_error(f"Échec du démarrage: {response.text}")
return
# Surveillance pendant quelques secondes
self.print_step("Surveillance de la capture via API")
for i in range(6):
time.sleep(1)
# Statut
response = requests.get(f"{self.api_base_url}/capture/status")
if response.status_code == 200:
status = response.json()['status']
# Éléments
response = requests.get(f"{self.api_base_url}/elements")
elements_count = 0
if response.status_code == 200:
elements_count = response.json()['count']
print(f" Seconde {i+1}: "
f"Capture: {'' if status['is_capturing'] else ''}, "
f"Éléments: {elements_count}")
# Test de screenshot
self.print_step("Récupération du screenshot via API")
response = requests.get(f"{self.api_base_url}/capture/screenshot")
if response.status_code == 200:
screenshot_data = response.json()
self.print_success("Screenshot récupéré")
self.print_info(f"Éléments dans le screenshot: {len(screenshot_data['elements'])}")
# Afficher quelques éléments
for i, elem in enumerate(screenshot_data['elements'][:3]):
bbox = elem['bbox']
print(f" {i+1}. {elem['type']}: "
f"pos=({bbox['x']}, {bbox['y']}) "
f"size={bbox['width']}x{bbox['height']} "
f"conf={elem['confidence']:.2f}")
# Arrêt de la capture
self.print_step("Arrêt de la capture via API")
response = requests.post(f"{self.api_base_url}/capture/stop")
if response.status_code == 200:
self.print_success("Capture arrêtée via API")
except Exception as e:
self.print_error(f"Erreur lors des tests API: {e}")
def demo_interaction_simulation(self):
"""Démonstration des interactions simulées"""
self.print_header("DÉMONSTRATION INTERACTIONS SIMULÉES")
try:
# Test de clic par coordonnées
self.print_step("Test de clic par coordonnées")
response = requests.post(
f"{self.api_base_url}/interact/click",
json={'x': 100, 'y': 100}
)
if response.status_code == 200:
result = response.json()
self.print_success(f"Clic simulé: {result['message']}")
else:
self.print_info("Clic non effectué (pyautogui non disponible ou erreur)")
# Test de saisie
self.print_step("Test de saisie de texte")
response = requests.post(
f"{self.api_base_url}/interact/type",
json={'text': 'Test RPA Vision V3 - Capture Réelle'}
)
if response.status_code == 200:
result = response.json()
self.print_success(f"Saisie simulée: {result['message']}")
else:
self.print_info("Saisie non effectuée (pyautogui non disponible ou erreur)")
# Test d'arrêt d'urgence
self.print_step("Test d'arrêt d'urgence")
response = requests.post(f"{self.api_base_url}/safety/emergency-stop")
if response.status_code == 200:
self.print_success("Arrêt d'urgence testé")
except Exception as e:
self.print_error(f"Erreur lors des tests d'interaction: {e}")
def demo_workflow_execution(self):
"""Démonstration d'exécution de workflow simple"""
self.print_header("DÉMONSTRATION WORKFLOW SIMPLE")
try:
# Workflow simple : clic + saisie + attente
workflow_actions = [
{'type': 'click', 'x': 200, 'y': 200},
{'type': 'wait', 'duration': 0.5},
{'type': 'type', 'text': 'Bonjour RPA Vision V3'},
{'type': 'wait', 'duration': 0.5},
{'type': 'click', 'x': 300, 'y': 300}
]
self.print_step("Exécution d'un workflow simple")
self.print_info("Actions du workflow:")
for i, action in enumerate(workflow_actions):
print(f" {i+1}. {action['type']}: {action}")
response = requests.post(
f"{self.api_base_url}/workflow/execute",
json={'actions': workflow_actions}
)
if response.status_code == 200:
result = response.json()
self.print_success("Workflow exécuté")
summary = result['summary']
print(f" 📊 Résumé: {summary['successful_actions']}/{summary['total_actions']} "
f"actions réussies ({summary['success_rate']:.1%})")
# Détails des résultats
for res in result['results']:
status = "" if res['success'] else ""
print(f" {status} Action {res['action_index']+1} ({res['type']}): "
f"{res.get('message', res.get('error', 'N/A'))}")
else:
self.print_error(f"Échec du workflow: {response.text}")
except Exception as e:
self.print_error(f"Erreur lors de l'exécution du workflow: {e}")
def run_complete_demo(self):
"""Exécuter la démonstration complète"""
self.print_header("DÉMONSTRATION COMPLÈTE - SYSTÈME DE CAPTURE RÉELLE")
print("RPA Vision V3 - Système de Capture d'Écran et Interaction Réelle")
print("Auteur : Dom, Alice, Kiro - 8 janvier 2026")
try:
# 1. Service direct
self.demo_service_direct()
# Pause entre les démos
self.print_info("Pause de 2 secondes entre les démonstrations...")
time.sleep(2)
# 2. API REST
self.demo_api_endpoints()
# Pause
time.sleep(1)
# 3. Interactions
self.demo_interaction_simulation()
# Pause
time.sleep(1)
# 4. Workflow
self.demo_workflow_execution()
# Résumé final
self.print_header("DÉMONSTRATION TERMINÉE")
self.print_success("Toutes les démonstrations ont été exécutées")
self.print_info("Fonctionnalités testées:")
print(" ✅ Service de capture d'écran réelle")
print(" ✅ Détection d'éléments UI en temps réel")
print(" ✅ API REST complète")
print(" ✅ Interactions simulées (clic, saisie)")
print(" ✅ Exécution de workflows simples")
print(" ✅ Contrôles de sécurité")
self.print_info("Le système de capture d'écran réelle est opérationnel ! 🚀")
except KeyboardInterrupt:
self.print_info("Démonstration interrompue par l'utilisateur")
except Exception as e:
self.print_error(f"Erreur générale: {e}")
finally:
# Nettoyage final
try:
self.service.cleanup()
requests.post(f"{self.api_base_url}/safety/emergency-stop", timeout=2)
except:
pass
def main():
"""Fonction principale"""
print("🎯 Démarrage de la démonstration du système de capture d'écran réelle")
# Vérifications préliminaires
try:
import mss
print("✅ MSS disponible pour la capture d'écran")
except ImportError:
print("❌ MSS non disponible - capture d'écran limitée")
try:
import pyautogui
print("✅ PyAutoGUI disponible pour les interactions")
except ImportError:
print("⚠️ PyAutoGUI non disponible - interactions simulées seulement")
# Lancer la démonstration
demo = RealScreenCaptureDemo()
demo.run_complete_demo()
if __name__ == "__main__":
main()

View File

@@ -1,167 +0,0 @@
#!/usr/bin/env python3
"""
Démonstration de la validation de sécurité
Montre comment le système refuse de démarrer avec une configuration insécurisée en production.
"""
import os
import sys
from pathlib import Path
# Add current directory to path for imports
sys.path.insert(0, str(Path(__file__).parent))
from core.security import (
validate_production_security,
get_security_config,
generate_secure_key,
check_security_requirements,
ProductionSecurityError
)
def demo_insecure_production():
"""Démontre le refus de configuration insécurisée en production."""
print("🚨 Demo: Insecure Production Configuration")
print("=" * 50)
# Simuler l'environnement de production
os.environ["ENVIRONMENT"] = "production"
os.environ["ENCRYPTION_PASSWORD"] = "rpa_vision_v3_default_key" # Clé par défaut
os.environ["SECRET_KEY"] = "dev-key-change-in-production" # Clé par défaut
print("Environment: PRODUCTION")
print("Encryption Password: rpa_vision_v3_default_key (DEFAULT)")
print("Secret Key: dev-key-change-in-production (DEFAULT)")
print()
try:
config = get_security_config()
validate_production_security(config)
print("❌ This should not happen - insecure config was accepted!")
except ProductionSecurityError as e:
print("✅ Security validation correctly REJECTED the insecure configuration:")
print(f" {e}")
print()
def demo_secure_production():
"""Démontre l'acceptation de configuration sécurisée en production."""
print("✅ Demo: Secure Production Configuration")
print("=" * 50)
# Générer des clés sécurisées
secure_encryption_key = generate_secure_key(32)
secure_secret_key = generate_secure_key(32)
os.environ["ENVIRONMENT"] = "production"
os.environ["ENCRYPTION_PASSWORD"] = secure_encryption_key
os.environ["SECRET_KEY"] = secure_secret_key
os.environ["LOG_SENSITIVE_DATA"] = "false"
os.environ["STRICT_INPUT_VALIDATION"] = "true"
print("Environment: PRODUCTION")
print(f"Encryption Password: {secure_encryption_key[:8]}... (SECURE)")
print(f"Secret Key: {secure_secret_key[:8]}... (SECURE)")
print("Log Sensitive Data: false")
print("Strict Input Validation: true")
print()
try:
config = get_security_config()
validate_production_security(config)
print("✅ Security validation ACCEPTED the secure configuration")
except ProductionSecurityError as e:
print(f"❌ Secure configuration was rejected: {e}")
print()
def demo_development_flexibility():
"""Démontre la flexibilité en environnement de développement."""
print("🔧 Demo: Development Environment Flexibility")
print("=" * 50)
# Environnement de développement avec clés par défaut
os.environ["ENVIRONMENT"] = "development"
os.environ["ENCRYPTION_PASSWORD"] = "rpa_vision_v3_default_key"
os.environ["SECRET_KEY"] = "dev-key-change-in-production"
print("Environment: DEVELOPMENT")
print("Encryption Password: rpa_vision_v3_default_key (DEFAULT)")
print("Secret Key: dev-key-change-in-production (DEFAULT)")
print()
try:
config = get_security_config()
validate_production_security(config)
print("✅ Development environment allows default keys for convenience")
except ProductionSecurityError as e:
print(f"❌ Development should be flexible: {e}")
print()
def demo_security_requirements():
"""Démontre la vérification des exigences de sécurité."""
print("📋 Demo: Security Requirements Check")
print("=" * 50)
# Vérifier les exigences en production
os.environ["ENVIRONMENT"] = "production"
secure_key = generate_secure_key(32)
os.environ["ENCRYPTION_PASSWORD"] = secure_key
os.environ["SECRET_KEY"] = secure_key
requirements = check_security_requirements()
print("Security Requirements Status:")
for requirement, status in requirements.items():
status_icon = "" if status else ""
print(f" {status_icon} {requirement}: {status}")
print()
def cleanup_environment():
"""Nettoie les variables d'environnement."""
test_vars = [
"ENVIRONMENT",
"ENCRYPTION_PASSWORD",
"SECRET_KEY",
"LOG_SENSITIVE_DATA",
"STRICT_INPUT_VALIDATION"
]
for var in test_vars:
os.environ.pop(var, None)
def main():
"""Fonction principale de démonstration."""
print("🎯 RPA Vision V3 - Security Validation Demo")
print("=" * 60)
print()
try:
# Demo 1: Configuration insécurisée en production
demo_insecure_production()
# Demo 2: Configuration sécurisée en production
demo_secure_production()
# Demo 3: Flexibilité en développement
demo_development_flexibility()
# Demo 4: Vérification des exigences
demo_security_requirements()
print("🎉 Security validation demo completed!")
print()
print("Key takeaways:")
print(" • Production environments require secure configuration")
print(" • Default keys are rejected in production")
print(" • Development environments are more flexible")
print(" • Security requirements can be checked programmatically")
finally:
cleanup_environment()
if __name__ == "__main__":
main()

View File

@@ -1,332 +0,0 @@
#!/usr/bin/env python3
"""
Demo script for Self-Healing Workflows system.
This script demonstrates the key features of the self-healing system.
"""
import sys
from pathlib import Path
from datetime import datetime
# Add project root to path
sys.path.insert(0, str(Path(__file__).parent))
from core.healing.healing_engine import SelfHealingEngine
from core.healing.learning_repository import LearningRepository
from core.healing.confidence_scorer import ConfidenceScorer
from core.healing.recovery_logger import RecoveryLogger
from core.healing.models import RecoveryContext, RecoveryResult
from core.healing.execution_integration import get_self_healing_integration
def print_header(title: str):
"""Print a formatted header."""
print("\n" + "=" * 70)
print(f" {title}")
print("=" * 70)
def demo_confidence_scorer():
"""Demonstrate confidence scoring."""
print_header("1. Confidence Scorer Demo")
scorer = ConfidenceScorer()
# Test text similarity
print("\n📊 Text Similarity:")
pairs = [
("Submit", "Submit"),
("Submit", "Send"),
("Submit", "Cancel"),
]
for text1, text2 in pairs:
similarity = scorer._text_similarity(text1, text2)
print(f" '{text1}' vs '{text2}': {similarity:.3f}")
# Test confidence calculation
print("\n📊 Recovery Confidence:")
context = RecoveryContext(
original_action='click',
target_element='Submit Button',
failure_reason='element_not_found',
screenshot_path='/tmp/test.png',
workflow_id='demo_workflow',
node_id='node_1',
attempt_count=1
)
strategies = ['semantic_variant', 'spatial_fallback', 'timing_adaptation']
for strategy in strategies:
confidence = scorer.calculate_recovery_confidence(strategy, context, 0.8)
print(f" {strategy}: {confidence:.3f}")
def demo_learning_repository():
"""Demonstrate learning repository."""
print_header("2. Learning Repository Demo")
import tempfile
temp_dir = Path(tempfile.mkdtemp())
repo = LearningRepository(temp_dir)
# Store some patterns
print("\n💾 Storing recovery patterns...")
contexts_and_results = [
(
RecoveryContext(
original_action='click',
target_element='Submit',
failure_reason='element_not_found',
screenshot_path='/tmp/test1.png',
workflow_id='workflow_1',
node_id='node_1',
attempt_count=1,
metadata={'element_type': 'button'}
),
RecoveryResult(
success=True,
strategy_used='semantic_variant',
new_element='Send',
confidence_score=0.85
)
),
(
RecoveryContext(
original_action='click',
target_element='Login',
failure_reason='element_moved',
screenshot_path='/tmp/test2.png',
workflow_id='workflow_1',
node_id='node_2',
attempt_count=1,
metadata={'element_type': 'button'}
),
RecoveryResult(
success=True,
strategy_used='spatial_fallback',
confidence_score=0.75
)
),
]
for context, result in contexts_and_results:
repo.store_pattern(context, result)
print(f" ✅ Stored: {result.strategy_used} for {context.failure_reason}")
# Retrieve patterns
print(f"\n📚 Total patterns stored: {len(repo.get_all_patterns())}")
for pattern in repo.get_all_patterns():
print(f" - {pattern.recovery_strategy}: {pattern.success_rate:.1%} success rate")
# Cleanup
import shutil
shutil.rmtree(temp_dir, ignore_errors=True)
def demo_recovery_strategies():
"""Demonstrate recovery strategies."""
print_header("3. Recovery Strategies Demo")
from core.healing.strategies import (
SemanticVariantStrategy,
SpatialFallbackStrategy,
TimingAdaptationStrategy,
FormatTransformationStrategy
)
# Semantic Variants
print("\n🔤 Semantic Variant Strategy:")
strategy = SemanticVariantStrategy()
variants = strategy._get_semantic_variants('submit')
print(f" 'submit'{', '.join(variants[:5])}")
variants = strategy._get_semantic_variants('login')
print(f" 'login'{', '.join(variants[:5])}")
# Spatial Fallback
print("\n📍 Spatial Fallback Strategy:")
strategy = SpatialFallbackStrategy()
print(f" Search radii: {strategy.search_radii} pixels")
# Timing Adaptation
print("\n⏱️ Timing Adaptation Strategy:")
strategy = TimingAdaptationStrategy()
print(f" Min wait: {strategy.min_wait}s")
print(f" Max wait: {strategy.max_wait}s")
print(f" Adaptation factor: {strategy.adaptation_factor}x")
# Format Transformation
print("\n🔄 Format Transformation Strategy:")
strategy = FormatTransformationStrategy()
print(f" Date formats: {len(strategy.date_formats)} variations")
print(f" Phone formats: {len(strategy.phone_formats)} variations")
def demo_self_healing_engine():
"""Demonstrate self-healing engine."""
print_header("4. Self-Healing Engine Demo")
import tempfile
temp_dir = Path(tempfile.mkdtemp())
engine = SelfHealingEngine(storage_path=temp_dir)
print(f"\n🔧 Engine initialized with {len(engine.recovery_strategies)} strategies")
# Create a recovery context
context = RecoveryContext(
original_action='click',
target_element='Submit Button',
failure_reason='element_not_found',
screenshot_path='/tmp/demo.png',
workflow_id='demo_workflow',
node_id='demo_node',
attempt_count=1,
confidence_threshold=0.7
)
print("\n🔍 Getting recovery suggestions...")
suggestions = engine.get_recovery_suggestions(context)
for i, suggestion in enumerate(suggestions, 1):
print(f" {i}. {suggestion.strategy}")
print(f" Confidence: {suggestion.confidence:.3f}")
print(f" Description: {suggestion.description}")
print(f" Est. time: {suggestion.estimated_time}s")
# Cleanup
import shutil
shutil.rmtree(temp_dir, ignore_errors=True)
def demo_integration():
"""Demonstrate integration layer."""
print_header("5. Integration Layer Demo")
import tempfile
temp_dir = Path(tempfile.mkdtemp())
healing = get_self_healing_integration(
storage_path=temp_dir / 'healing',
log_path=temp_dir / 'logs',
enabled=True
)
print("\n✅ Self-healing integration initialized")
print(f" Enabled: {healing.enabled}")
# Get statistics
stats = healing.get_statistics()
print(f"\n📊 Statistics:")
print(f" Total attempts: {stats.get('total_attempts', 0)}")
print(f" Successful recoveries: {stats.get('successful_recoveries', 0)}")
# Cleanup
import shutil
shutil.rmtree(temp_dir, ignore_errors=True)
def demo_complete_workflow():
"""Demonstrate a complete recovery workflow."""
print_header("6. Complete Recovery Workflow Demo")
import tempfile
temp_dir = Path(tempfile.mkdtemp())
# Initialize
healing = get_self_healing_integration(
storage_path=temp_dir / 'healing',
log_path=temp_dir / 'logs',
enabled=True
)
print("\n📝 Simulating workflow execution failure...")
# Simulate a failure
from core.execution.action_executor import ExecutionResult, ExecutionStatus
action_info = {
'action': 'click',
'target': 'Submit Button',
'element_type': 'button'
}
execution_result = ExecutionResult(
status=ExecutionStatus.TARGET_NOT_FOUND,
message='Element not found: Submit Button',
duration_ms=100
)
print(f" ❌ Action failed: {execution_result.message}")
# Attempt recovery
print("\n🔧 Attempting recovery...")
recovery = healing.handle_execution_failure(
action_info=action_info,
execution_result=execution_result,
workflow_id='demo_workflow',
node_id='demo_node',
screenshot_path='/tmp/demo.png',
attempt_count=1
)
if recovery:
if recovery.success:
print(f" ✅ Recovery successful!")
print(f" Strategy: {recovery.strategy_used}")
print(f" Confidence: {recovery.confidence_score:.3f}")
print(f" Time: {recovery.execution_time:.3f}s")
else:
print(f" ❌ Recovery failed")
print(f" Reason: {recovery.error_message}")
# Get insights
print("\n💡 Insights:")
insights = healing.get_insights()
if insights:
for insight in insights:
print(f" - {insight}")
else:
print(" - No insights yet (need more data)")
# Cleanup
import shutil
shutil.rmtree(temp_dir, ignore_errors=True)
def main():
"""Run all demos."""
print("\n" + "🎯" * 35)
print(" SELF-HEALING WORKFLOWS - DEMONSTRATION")
print("🎯" * 35)
try:
demo_confidence_scorer()
demo_learning_repository()
demo_recovery_strategies()
demo_self_healing_engine()
demo_integration()
demo_complete_workflow()
print("\n" + "=" * 70)
print(" ✅ All demos completed successfully!")
print("=" * 70)
print("\n📚 For more information, see:")
print(" - SELF_HEALING_IMPLEMENTATION.md")
print(" - SELF_HEALING_QUICKSTART.md")
print("\n")
except Exception as e:
print(f"\n❌ Error during demo: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -1,77 +0,0 @@
#!/usr/bin/env python3
"""
Démonstration du système de cleanup
Montre comment le système nettoie proprement toutes les ressources.
"""
import logging
import sys
import time
from pathlib import Path
# Add current directory to path for imports
sys.path.insert(0, str(Path(__file__).parent))
from core.system import initialize_system_cleanup, shutdown_system
def main():
"""Démonstration du cleanup système."""
print("🎯 RPA Vision V3 - System Cleanup Demo")
print("=" * 50)
# Configuration du logging pour voir les détails
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
print("1. Initializing system with cleanup...")
initialize_system_cleanup()
print("\n2. System is now running with automatic cleanup...")
print(" - Memory managers registered")
print(" - GPU resource managers registered")
print(" - Analytics system registered")
print(" - Signal handlers installed")
print("\n3. Simulating some work...")
# Simuler du travail avec les systèmes
try:
from core.execution.memory_cache import get_memory_manager
from core.gpu.gpu_resource_manager import get_gpu_resource_manager
# Utiliser le memory manager
memory_manager = get_memory_manager(enable_monitoring=False)
print(f" ✓ Memory manager active: {memory_manager.max_memory_mb}MB limit")
# Utiliser le GPU manager
gpu_manager = get_gpu_resource_manager()
status = gpu_manager.get_status()
print(f" ✓ GPU manager active: {status.execution_mode} mode")
# Simuler du travail
time.sleep(1)
except Exception as e:
print(f" ⚠ Some systems not available: {e}")
print("\n4. Testing cleanup (Ctrl+C to trigger signal cleanup)...")
print(" Press Ctrl+C to see signal-based cleanup in action")
print(" Or wait 5 seconds for programmatic cleanup...")
try:
time.sleep(5)
print("\n5. Triggering programmatic cleanup...")
shutdown_system()
except KeyboardInterrupt:
print("\n5. Signal received! Triggering cleanup...")
shutdown_system()
print("\n✅ Cleanup demo completed!")
print("All resources have been properly cleaned up.")
if __name__ == "__main__":
main()

View File

@@ -1,381 +0,0 @@
#!/usr/bin/env python3
"""
Démonstration du système de nommage intelligent des workflows
Ce script montre comment utiliser les nouvelles fonctionnalités de nommage
intelligent et de capture enrichie de l'Agent V0.
"""
import sys
import os
import time
import tempfile
from datetime import datetime
# Add agent_v0 to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'agent_v0'))
from agent_v0.enhanced_raw_session import EnhancedRawSession
from agent_v0.workflow_namer import WorkflowNamer
from agent_v0.ui_dialogs import show_workflow_name_dialog
def demo_basic_naming():
"""Démonstration du nommage de base"""
print("=== Démonstration du Nommage de Base ===")
namer = WorkflowNamer()
# Créer une session de test
session = EnhancedRawSession.create_enhanced(
user_id="demo_user",
user_label="Utilisateur Démo",
workflow_name="Session_Demo"
)
# Simuler des interactions avec un CRM
print("Simulation d'interactions CRM...")
# Clic sur un bouton "Nouveau Client"
session.add_enhanced_mouse_click_event(
button="left",
pos=[150, 100],
window_title="CRM Pro - Gestion Clients",
app_name="CRM_Pro",
screenshot_id="shot_001",
element_type="button",
element_text="Nouveau Client",
confidence=0.95
)
# Saisie du nom du client
session.add_enhanced_key_event(
keys=["Jean", "space", "Dupont"],
window_title="CRM Pro - Nouveau Client",
app_name="CRM_Pro",
screenshot_id="shot_002",
text_content="Jean Dupont",
input_method="typing",
confidence=0.9
)
# Saisie de l'email
session.add_enhanced_key_event(
keys=["jean.dupont@email.com"],
window_title="CRM Pro - Nouveau Client",
app_name="CRM_Pro",
screenshot_id="shot_003",
text_content="jean.dupont@email.com",
input_method="typing",
confidence=0.9
)
# Clic sur "Sauvegarder"
session.add_enhanced_mouse_click_event(
button="left",
pos=[200, 400],
window_title="CRM Pro - Nouveau Client",
app_name="CRM_Pro",
screenshot_id="shot_004",
element_type="button",
element_text="Sauvegarder",
confidence=0.95
)
# Générer un nom intelligent
print("Génération du nom intelligent...")
intelligent_name = session.generate_intelligent_name()
print(f"Nom généré : {intelligent_name}")
# Analyser la session
analysis = session.analyze_session()
print(f"Type de workflow : {analysis.workflow_type}")
print(f"Application principale : {analysis.primary_application}")
print(f"Score de complexité : {analysis.complexity_score:.2f}")
# Évaluer la qualité
quality_score = session.get_workflow_quality_score()
suggestions = session.get_workflow_suggestions()
print(f"Score de qualité : {quality_score:.1%}")
print("Suggestions d'amélioration :")
for suggestion in suggestions:
print(f"{suggestion}")
return session
def demo_different_workflow_types():
"""Démonstration de différents types de workflows"""
print("\n=== Démonstration des Types de Workflows ===")
workflows = []
# 1. Workflow de connexion
print("\n1. Workflow de Connexion")
login_session = EnhancedRawSession.create_enhanced(
user_id="demo_user",
workflow_name="Demo_Login"
)
login_session.add_enhanced_mouse_click_event(
button="left", pos=[100, 200],
window_title="Gmail - Sign In", app_name="Chrome",
screenshot_id="login_001", element_type="input",
element_text="Email", confidence=0.9
)
login_session.add_enhanced_key_event(
keys=["user@example.com"], window_title="Gmail - Sign In",
app_name="Chrome", screenshot_id="login_002",
text_content="user@example.com", input_method="typing"
)
login_session.add_enhanced_mouse_click_event(
button="left", pos=[100, 250],
window_title="Gmail - Sign In", app_name="Chrome",
screenshot_id="login_003", element_type="input",
element_text="Password", confidence=0.9
)
login_name = login_session.generate_intelligent_name()
print(f"Nom généré : {login_name}")
workflows.append(("Connexion", login_session))
# 2. Workflow de navigation
print("\n2. Workflow de Navigation")
nav_session = EnhancedRawSession.create_enhanced(
user_id="demo_user",
workflow_name="Demo_Navigation"
)
# Plusieurs clics de navigation
for i, section in enumerate(["Dashboard", "Reports", "Settings", "Profile"]):
nav_session.add_enhanced_mouse_click_event(
button="left", pos=[50 + i*100, 50],
window_title=f"App - {section}", app_name="WebApp",
screenshot_id=f"nav_{i:03d}", element_type="link",
element_text=section, confidence=0.8
)
nav_name = nav_session.generate_intelligent_name()
print(f"Nom généré : {nav_name}")
workflows.append(("Navigation", nav_session))
# 3. Workflow de recherche
print("\n3. Workflow de Recherche")
search_session = EnhancedRawSession.create_enhanced(
user_id="demo_user",
workflow_name="Demo_Search"
)
search_session.add_enhanced_mouse_click_event(
button="left", pos=[300, 50],
window_title="Google", app_name="Chrome",
screenshot_id="search_001", element_type="input",
element_text="Search", confidence=0.9
)
search_session.add_enhanced_key_event(
keys=["Python", "space", "tutorial"],
window_title="Google", app_name="Chrome",
screenshot_id="search_002", text_content="Python tutorial",
input_method="typing"
)
search_name = search_session.generate_intelligent_name()
print(f"Nom généré : {search_name}")
workflows.append(("Recherche", search_session))
return workflows
def demo_quality_assessment():
"""Démonstration de l'évaluation de qualité"""
print("\n=== Démonstration de l'Évaluation de Qualité ===")
# Workflow de haute qualité
print("\n1. Workflow de Haute Qualité")
high_quality = EnhancedRawSession.create_enhanced(
user_id="demo_user",
workflow_name="High_Quality_Demo"
)
# Nombreuses interactions variées
for i in range(5):
high_quality.add_enhanced_mouse_click_event(
button="left", pos=[100 + i*50, 100 + i*30],
window_title="Complex App - Form", app_name="ComplexApp",
screenshot_id=f"hq_{i:03d}", element_type="input",
element_text=f"Field {i+1}", confidence=0.9
)
high_quality.add_enhanced_key_event(
keys=[f"Value_{i+1}"], window_title="Complex App - Form",
app_name="ComplexApp", screenshot_id=f"hq_key_{i:03d}",
text_content=f"Value_{i+1}", input_method="typing"
)
hq_score = high_quality.get_workflow_quality_score()
hq_suggestions = high_quality.get_workflow_suggestions()
print(f"Score de qualité : {hq_score:.1%}")
print("Suggestions :")
for suggestion in hq_suggestions:
print(f"{suggestion}")
# Workflow de basse qualité
print("\n2. Workflow de Basse Qualité")
low_quality = EnhancedRawSession.create_enhanced(
user_id="demo_user",
workflow_name="Low_Quality_Demo"
)
# Une seule interaction simple
low_quality.add_enhanced_mouse_click_event(
button="left", pos=[100, 100],
window_title="Simple App", app_name="SimpleApp",
screenshot_id="lq_001", element_type="button",
element_text="Click", confidence=0.5
)
lq_score = low_quality.get_workflow_quality_score()
lq_suggestions = low_quality.get_workflow_suggestions()
print(f"Score de qualité : {lq_score:.1%}")
print("Suggestions :")
for suggestion in lq_suggestions:
print(f"{suggestion}")
def demo_serialization():
"""Démonstration de la sérialisation enrichie"""
print("\n=== Démonstration de la Sérialisation ===")
session = EnhancedRawSession.create_enhanced(
user_id="demo_user",
workflow_name="Serialization_Demo"
)
# Ajouter quelques événements
session.add_enhanced_mouse_click_event(
button="left", pos=[100, 200],
window_title="Test App", app_name="TestApp",
screenshot_id="ser_001", element_type="button",
element_text="Test Button", confidence=0.9
)
# Fermer avec analyse
session.close_with_analysis()
# Sauvegarder dans un dossier temporaire
with tempfile.TemporaryDirectory() as temp_dir:
json_path = session.save_enhanced_json(temp_dir)
print(f"Session sauvegardée : {json_path}")
# Lire et afficher le contenu
import json
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
print(f"Clés principales : {list(data.keys())}")
if 'workflow_metadata' in data:
metadata = data['workflow_metadata']
print(f"Nom du workflow : {metadata.get('workflow_name')}")
print(f"Type : {metadata.get('workflow_type')}")
print(f"Application : {metadata.get('primary_application')}")
print(f"Score de complexité : {metadata.get('complexity_score', 0):.2f}")
print(f"Nombre d'événements : {len(data.get('events', []))}")
print(f"Nombre d'événements enrichis : {len(data.get('enhanced_events', []))}")
print(f"Score de qualité : {data.get('quality_score', 0):.1%}")
def demo_ui_integration():
"""Démonstration de l'intégration UI (si Qt disponible)"""
print("\n=== Démonstration de l'Intégration UI ===")
try:
from PyQt5.QtWidgets import QApplication
# Vérifier si Qt est disponible
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
print("Qt5 disponible - démonstration des dialogues")
# Simuler des noms existants
existing_names = [
"Saisie_Client_CRM",
"Navigation_Dashboard_Admin",
"Recherche_Produits_Catalogue"
]
print("Noms existants :")
for name in existing_names:
print(f"{name}")
print("\nPour tester le dialogue interactif, décommentez le code suivant :")
print("# result = show_workflow_name_dialog('Nouveau_Workflow_Demo', existing_names)")
print("# print(f'Nom sélectionné : {result}')")
# Démonstration non-interactive
# result = show_workflow_name_dialog("Nouveau_Workflow_Demo", existing_names)
# print(f"Nom sélectionné : {result}")
except ImportError:
print("Qt5 non disponible - utilisation des fallbacks")
print("Les dialogues utiliseront des interfaces texte simplifiées")
def main():
"""Fonction principale de démonstration"""
print("Démonstration du Système de Nommage Intelligent des Workflows")
print("=" * 70)
try:
# Démonstrations principales
session = demo_basic_naming()
workflows = demo_different_workflow_types()
demo_quality_assessment()
demo_serialization()
demo_ui_integration()
# Résumé final
print("\n" + "=" * 70)
print("Résumé de la Démonstration")
print("=" * 70)
print(f"✓ Session de base créée : {session.session_id}")
print(f"{len(workflows)} types de workflows démontrés")
print("✓ Évaluation de qualité testée")
print("✓ Sérialisation enrichie validée")
print("✓ Intégration UI vérifiée")
print("\nFonctionnalités démontrées :")
print(" • Génération automatique de noms intelligents")
print(" • Détection de types de workflows")
print(" • Analyse de qualité avec suggestions")
print(" • Sérialisation enrichie avec métadonnées")
print(" • Compatibilité avec l'interface utilisateur")
print("\nPour utiliser le système :")
print(" 1. Remplacez TrayApp par EnhancedTrayApp")
print(" 2. Les dialogues de nommage s'ouvriront automatiquement")
print(" 3. Les workflows seront organisés avec des noms descriptifs")
print(" 4. Consultez le guide : agent_v0/WORKFLOW_NAMING_GUIDE.md")
return 0
except Exception as e:
print(f"Erreur lors de la démonstration : {e}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,19 +0,0 @@
# Déploiement Cleanup Complet
# Suppression des JSON bruts inexploitables (sans screenshots)
# 1. Déployer
sudo cp /home/dom/ai/rpa_vision_v3/processing_pipeline.py /opt/rpa_vision_v3/server/processing_pipeline.py
sudo chown rpa:rpa /opt/rpa_vision_v3/server/processing_pipeline.py
# 2. Redémarrer le worker
sudo systemctl restart rpa-vision-v3-worker.service
# 3. Vérifier
systemctl status rpa-vision-v3-worker.service
# 4. Tester avec une nouvelle session
cd /home/dom/ai/rpa_vision_v3/agent_v0
./run.sh
# Attendre 1 minute après upload
# Vérifier que les JSON bruts sont bien supprimés:
ls /opt/rpa_vision_v3/data/training/sessions/$(date +%Y-%m-%d)/

View File

@@ -1,22 +0,0 @@
# Commandes de déploiement - Dashboard Phase 1
# Copier-coller ces commandes une par une
# 1. Sauvegarde
sudo cp /opt/rpa_vision_v3/web_dashboard/app.py /opt/rpa_vision_v3/web_dashboard/app.py.backup_phase1_$(date +%Y%m%d_%H%M%S)
# 2. Déploiement
sudo cp /home/dom/ai/rpa_vision_v3/web_dashboard_app.py /opt/rpa_vision_v3/web_dashboard/app.py
# 3. Permissions
sudo chown rpa:rpa /opt/rpa_vision_v3/web_dashboard/app.py
sudo chmod 644 /opt/rpa_vision_v3/web_dashboard/app.py
# 4. Redémarrage
sudo systemctl restart rpa-vision-v3-dashboard.service
# 5. Vérification
systemctl status rpa-vision-v3-dashboard.service
# 6. Tests
curl http://localhost:5001/api/screen_states | python3 -m json.tool | head -50
curl http://localhost:5001/api/agent/sessions | python3 -m json.tool | grep screenshots_count

View File

@@ -1,78 +0,0 @@
#!/bin/bash
# Script de déploiement - Dashboard Fix Phase 1
# Corrections : chemins screenshots + route API screen_states
set -e
echo "=========================================="
echo "DASHBOARD FIX - PHASE 1"
echo "=========================================="
echo ""
# Couleurs
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
DEV_FILE="/home/dom/ai/rpa_vision_v3/web_dashboard_app.py"
PROD_FILE="/opt/rpa_vision_v3/web_dashboard/app.py"
BACKUP_DIR="/opt/rpa_vision_v3/web_dashboard"
echo -e "${YELLOW}Modifications appliquées :${NC}"
echo " 1. Fix chemin screenshots : session_dir/{session_id}/shots/*.png"
echo " 2. Ajout route /api/screen_states (liste les 236 screen_states)"
echo " 3. Ajout route /api/screen_states/<session_id> (détails par session)"
echo ""
# Vérifier que le fichier dev existe
if [ ! -f "$DEV_FILE" ]; then
echo -e "${RED}❌ Fichier dev non trouvé: $DEV_FILE${NC}"
exit 1
fi
echo -e "${YELLOW}Étape 1/4 : Sauvegarde du fichier prod...${NC}"
BACKUP_FILE="${BACKUP_DIR}/app.py.backup_phase1_$(date +%Y%m%d_%H%M%S)"
sudo cp "$PROD_FILE" "$BACKUP_FILE"
echo -e "${GREEN}✓ Sauvegarde créée : $BACKUP_FILE${NC}"
echo ""
echo -e "${YELLOW}Étape 2/4 : Déploiement du fichier modifié...${NC}"
sudo cp "$DEV_FILE" "$PROD_FILE"
sudo chown rpa:rpa "$PROD_FILE"
sudo chmod 644 "$PROD_FILE"
echo -e "${GREEN}✓ Fichier déployé${NC}"
echo ""
echo -e "${YELLOW}Étape 3/4 : Redémarrage du service dashboard...${NC}"
sudo systemctl restart rpa-vision-v3-dashboard.service
sleep 2
echo -e "${GREEN}✓ Service redémarré${NC}"
echo ""
echo -e "${YELLOW}Étape 4/4 : Vérification du service...${NC}"
if sudo systemctl is-active --quiet rpa-vision-v3-dashboard.service; then
echo -e "${GREEN}✓ Service actif${NC}"
else
echo -e "${RED}❌ Service non actif !${NC}"
echo ""
echo "Logs d'erreur :"
sudo journalctl -u rpa-vision-v3-dashboard -n 20 --no-pager
exit 1
fi
echo ""
echo "=========================================="
echo -e "${GREEN}DÉPLOIEMENT RÉUSSI !${NC}"
echo "=========================================="
echo ""
echo "Tests à effectuer :"
echo " 1. curl http://localhost:5001/api/screen_states | python3 -m json.tool | head -50"
echo " 2. curl http://localhost:5001/api/agent/sessions | python3 -m json.tool | head -50"
echo " 3. Ouvrir http://localhost:5001 dans le navigateur"
echo ""
echo "Attendu :"
echo " - Screenshots count > 0 pour les sessions non nettoyées"
echo " - /api/screen_states retourne 236 screen_states"
echo " - Aucune erreur dans les anciennes routes"
echo ""

View File

@@ -1,14 +0,0 @@
# Déploiement Dashboard UI - Phase 2
# Ajout de l'onglet "Données Traitées"
# 1. Déployer le fichier HTML modifié
sudo cp /home/dom/ai/rpa_vision_v3/dashboard_index.html /opt/rpa_vision_v3/web_dashboard/templates/index.html
sudo chown rpa:rpa /opt/rpa_vision_v3/web_dashboard/templates/index.html
# 2. Redémarrer le dashboard (optionnel, le HTML est rechargé automatiquement)
sudo systemctl restart rpa-vision-v3-dashboard.service
# 3. Tester dans le navigateur
# Ouvrir http://localhost:5001
# Cliquer sur l'onglet "✅ Données Traitées"
# Vérifier que les 371 screen states s'affichent

View File

@@ -1,218 +0,0 @@
#!/bin/bash
# Script de déploiement de la correction api_tokens.py
# Basé sur le plan approuvé
set -e
echo "🚀 RPA Vision V3 - Déploiement Correction Authentification"
echo "==========================================================="
echo ""
# Vérifier qu'on est bien root ou avec sudo
if [ "$EUID" -ne 0 ]; then
echo "❌ Ce script doit être exécuté avec sudo"
echo "Usage: sudo bash deploy_fix.sh"
exit 1
fi
# ============================================================================
# ÉTAPE 1 : Sauvegarde du Code Actuel
# ============================================================================
echo "📦 ÉTAPE 1/5 : Sauvegarde du code production actuel"
echo "---------------------------------------------------"
BACKUP_DIR="/opt/rpa_vision_v3/core/security.backup_$(date +%Y%m%d_%H%M%S)"
echo "Création de la sauvegarde dans: $BACKUP_DIR"
if [ -d /opt/rpa_vision_v3/core/security ]; then
cp -r /opt/rpa_vision_v3/core/security "$BACKUP_DIR"
echo "✅ Sauvegarde créée avec succès"
else
echo "❌ Répertoire /opt/rpa_vision_v3/core/security introuvable!"
exit 1
fi
echo ""
# ============================================================================
# ÉTAPE 2 : Déploiement du Code Mis à Jour
# ============================================================================
echo "📂 ÉTAPE 2/5 : Déploiement du code mis à jour"
echo "----------------------------------------------"
SOURCE_FILE="/home/dom/ai/rpa_vision_v3/core/security/api_tokens.py"
DEST_FILE="/opt/rpa_vision_v3/core/security/api_tokens.py"
if [ ! -f "$SOURCE_FILE" ]; then
echo "❌ Fichier source introuvable: $SOURCE_FILE"
exit 1
fi
echo "Copie de $SOURCE_FILE"
echo " vers $DEST_FILE"
cp "$SOURCE_FILE" "$DEST_FILE"
echo "✅ Fichier copié"
# Correction des permissions
echo "Correction des permissions..."
chown rpa:rpa "$DEST_FILE"
chmod 644 "$DEST_FILE"
echo "✅ Permissions configurées (rpa:rpa, 644)"
echo ""
# ============================================================================
# ÉTAPE 3 : Redémarrage des Services
# ============================================================================
echo "🔄 ÉTAPE 3/5 : Redémarrage des services"
echo "---------------------------------------"
echo "Rechargement de la configuration systemd..."
systemctl daemon-reload
echo "✅ Configuration rechargée"
echo ""
SERVICES=(
"rpa-vision-v3-api.service"
"rpa-vision-v3-worker.service"
"rpa-vision-v3-dashboard.service"
)
echo "Redémarrage des services..."
for service in "${SERVICES[@]}"; do
echo -n "$service... "
if systemctl restart "$service" 2>/dev/null; then
echo "✅"
else
echo "❌ ERREUR"
echo ""
echo "Erreur lors du redémarrage de $service"
echo "Voir les logs: sudo journalctl -u $service -n 50"
exit 1
fi
done
echo ""
echo "⏳ Attente du démarrage complet (5 secondes)..."
sleep 5
echo ""
# Vérifier le statut des services
echo "Vérification du statut des services:"
for service in "${SERVICES[@]}"; do
status=$(systemctl is-active "$service" 2>/dev/null || echo "failed")
if [ "$status" = "active" ]; then
echo "$service: $status"
else
echo "$service: $status"
echo " Logs: sudo journalctl -u $service -n 20"
fi
done
echo ""
# ============================================================================
# ÉTAPE 4 : Vérification des Logs
# ============================================================================
echo "📋 ÉTAPE 4/5 : Vérification du chargement des tokens"
echo "----------------------------------------------------"
echo "Recherche de 'TokenManager initialized' dans les logs..."
TOKEN_LOG=$(journalctl -u rpa-vision-v3-api -n 100 --no-pager | grep -i "tokenmanager initialized" | tail -1)
if [ -n "$TOKEN_LOG" ]; then
echo "$TOKEN_LOG"
if echo "$TOKEN_LOG" | grep -q "2 admin tokens, 2 read-only tokens"; then
echo "✅ SUCCÈS : TokenManager a chargé 2 admin tokens et 2 read-only tokens"
elif echo "$TOKEN_LOG" | grep -q "0 admin tokens"; then
echo "❌ ÉCHEC : TokenManager affiche 0 admin tokens"
echo " Le code n'a peut-être pas été mis à jour correctement"
echo ""
echo "Vérification du fichier déployé:"
grep -n "prod_admin_token" "$DEST_FILE" | head -2
else
echo "⚠️ AVERTISSEMENT : Nombre de tokens inattendu"
echo "$TOKEN_LOG"
fi
else
echo "❌ Aucune ligne 'TokenManager initialized' trouvée dans les logs"
echo " Le service ne s'est peut-être pas démarré correctement"
echo ""
echo "Dernières lignes des logs:"
journalctl -u rpa-vision-v3-api -n 10 --no-pager
fi
echo ""
# ============================================================================
# ÉTAPE 5 : Test de l'API
# ============================================================================
echo "🌐 ÉTAPE 5/5 : Test de l'authentification API"
echo "---------------------------------------------"
echo "Test 1: Requête sans authentification (devrait retourner unauthorized)"
RESPONSE_NO_AUTH=$(curl -s http://localhost:8000/api/traces/status 2>/dev/null || echo '{"error":"connection_failed"}')
echo " Réponse: $RESPONSE_NO_AUTH"
if echo "$RESPONSE_NO_AUTH" | grep -q "unauthorized"; then
echo " ✅ Comportement correct (unauthorized sans token)"
else
echo " ⚠️ Réponse inattendue"
fi
echo ""
echo "Test 2: Requête avec token admin"
ADMIN_TOKEN="73cf0db73f9a5064e79afebba96c85338be65cc2060b9c1d42c3ea5dd7d4e490"
RESPONSE_WITH_AUTH=$(curl -s -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:8000/api/traces/status 2>/dev/null || echo '{"error":"connection_failed"}')
echo " Réponse: $RESPONSE_WITH_AUTH"
if echo "$RESPONSE_WITH_AUTH" | grep -q '"status"'; then
echo " ✅ SUCCÈS : Authentification fonctionnelle!"
echo ""
echo "════════════════════════════════════════════════════════════"
echo "✅ DÉPLOIEMENT RÉUSSI - Authentification Opérationnelle"
echo "════════════════════════════════════════════════════════════"
elif echo "$RESPONSE_WITH_AUTH" | grep -q "unauthorized"; then
echo " ❌ ÉCHEC : Authentification retourne toujours unauthorized"
echo ""
echo "════════════════════════════════════════════════════════════"
echo "❌ DÉPLOIEMENT INCOMPLET - Investigation Nécessaire"
echo "════════════════════════════════════════════════════════════"
echo ""
echo "💡 Actions de débogage:"
echo " 1. Vérifier les logs: sudo journalctl -u rpa-vision-v3-api -n 100"
echo " 2. Vérifier le code déployé: grep -n 'prod_admin_token' $DEST_FILE"
echo " 3. Restaurer si nécessaire: sudo cp -r $BACKUP_DIR /opt/rpa_vision_v3/core/security"
else
echo " ⚠️ Réponse inattendue: $RESPONSE_WITH_AUTH"
fi
echo ""
# ============================================================================
# RÉSUMÉ
# ============================================================================
echo "📊 RÉSUMÉ DU DÉPLOIEMENT"
echo "========================"
echo ""
echo "Sauvegarde: $BACKUP_DIR"
echo "Code déployé: $DEST_FILE"
echo ""
echo "Services:"
for service in "${SERVICES[@]}"; do
status=$(systemctl is-active "$service" 2>/dev/null || echo "failed")
echo " - $service: $status"
done
echo ""
if echo "$RESPONSE_WITH_AUTH" | grep -q '"status"'; then
echo "✅ Statut Global: SUCCÈS"
echo ""
echo "🎯 Prochaines étapes:"
echo " 1. Mettre à jour .env.local: bash fix_tokens_dev.sh"
echo " 2. Tester l'agent: cd agent_v0 && ./run.sh"
echo " 3. Vérifier l'upload: ls -lh /opt/rpa_vision_v3/data/training/sessions/"
else
echo "⚠️ Statut Global: ATTENTION REQUISE"
echo ""
echo "💡 Si le problème persiste, restaurer la sauvegarde:"
echo " sudo cp -r $BACKUP_DIR /opt/rpa_vision_v3/core/security"
echo " sudo systemctl restart rpa-vision-v3-*.service"
fi
echo ""

Some files were not shown because too many files have changed in this diff Show More