feat: chat unifié, GestureCatalog, Copilot, Léa UI, extraction données, vérification replay
Refonte majeure du système Agent Chat et ajout de nombreux modules : - Chat unifié : suppression du dual Workflows/Agent Libre, tout passe par /api/chat avec résolution en 3 niveaux (workflow → geste → "montre-moi") - GestureCatalog : 38 raccourcis clavier universels Windows avec matching sémantique, substitution automatique dans les replays, et endpoint /api/gestures - Mode Copilot : exécution pas-à-pas des workflows avec validation humaine via WebSocket (approve/skip/abort) avant chaque action - Léa UI (agent_v0/lea_ui/) : interface PyQt5 pour Windows avec overlay transparent pour feedback visuel pendant le replay - Data Extraction (core/extraction/) : moteur d'extraction visuelle de données (OCR + VLM → SQLite), avec schémas YAML et export CSV/Excel - ReplayVerifier (agent_v0/server_v1/) : vérification post-action par comparaison de screenshots, avec logique de retry (max 3) - IntentParser durci : meilleur fallback regex, type GREETING, patterns améliorés - Dashboard : nouvelles pages gestures, streaming, extractions - Tests : 63 tests GestureCatalog, 47 tests extraction, corrections tests existants - Dépréciation : /api/agent/plan et /api/agent/execute retournent HTTP 410, suppression du code hardcodé _plan_to_replay_actions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -67,7 +67,8 @@ def test_action_executor_click_position():
|
||||
action = Mock()
|
||||
action.type = ActionType.MOUSE_CLICK
|
||||
action.target = Mock()
|
||||
action.params = None
|
||||
action.parameters = {}
|
||||
action.params = {}
|
||||
|
||||
# Mock screen state
|
||||
screen_state = Mock()
|
||||
@@ -122,18 +123,18 @@ def test_target_resolver_position_matching():
|
||||
|
||||
# Position de recherche proche de elem3
|
||||
search_position = (170, 170)
|
||||
|
||||
# Mock screen state avec nos éléments
|
||||
screen_state = Mock()
|
||||
screen_state.ui_elements = elements
|
||||
|
||||
|
||||
# Mock context avec spatial_index=None pour forcer le fallback linéaire
|
||||
mock_context = Mock()
|
||||
mock_context.workflow_context = {"spatial_index": None}
|
||||
|
||||
# Mock _get_ui_elements pour retourner nos éléments
|
||||
resolver = TargetResolver(position_tolerance=50)
|
||||
with patch.object(resolver, '_get_ui_elements', return_value=elements):
|
||||
|
||||
|
||||
# Résoudre par position
|
||||
result = resolver._resolve_by_position(search_position, elements, Mock())
|
||||
|
||||
result = resolver._resolve_by_position(search_position, elements, mock_context)
|
||||
|
||||
# Devrait trouver elem3 (distance ≈ 14)
|
||||
assert result is not None
|
||||
assert result.element.element_id == "elem3"
|
||||
@@ -142,21 +143,24 @@ def test_target_resolver_position_matching():
|
||||
def test_target_resolver_proximity_filter():
|
||||
"""Test que le filtre de proximité utilise les bons calculs de centre"""
|
||||
|
||||
# Élément ancre au centre (100, 120) -> centre (100, 120)
|
||||
anchor = MockUIElement("anchor", (100, 120, 0, 0))
|
||||
|
||||
# Éléments à tester
|
||||
# Élément ancre: bbox (90, 110, 20, 20) -> centre (100, 120)
|
||||
anchor = MockUIElement("anchor", (90, 110, 20, 20))
|
||||
|
||||
# Éléments à tester (distances au centre de l'ancre (100, 120)):
|
||||
# near: centre (125, 125), distance = sqrt(25² + 5²) ≈ 25.5
|
||||
# medium: centre (130, 130), distance = sqrt(30² + 10²) ≈ 31.6
|
||||
# far: centre (205, 205), distance = sqrt(105² + 85²) ≈ 135.1
|
||||
elements = [
|
||||
MockUIElement("near", (120, 120, 10, 10)), # centre: (125, 125), distance ≈ 25
|
||||
MockUIElement("medium", (140, 140, 10, 10)), # centre: (145, 145), distance ≈ 35
|
||||
MockUIElement("far", (200, 200, 10, 10)), # centre: (205, 205), distance ≈ 120
|
||||
MockUIElement("near", (120, 120, 10, 10)),
|
||||
MockUIElement("medium", (125, 125, 10, 10)),
|
||||
MockUIElement("far", (200, 200, 10, 10)),
|
||||
]
|
||||
|
||||
|
||||
resolver = TargetResolver()
|
||||
|
||||
|
||||
# Filtrer avec distance max = 50
|
||||
filtered = resolver._filter_by_proximity(elements, anchor, max_distance=50)
|
||||
|
||||
|
||||
# Seuls "near" et "medium" devraient être dans le résultat
|
||||
filtered_ids = [elem.element_id for elem in filtered]
|
||||
assert "near" in filtered_ids
|
||||
|
||||
Reference in New Issue
Block a user