feat: unification VWB ↔ Léa — import/export bidirectionnel
- Workflows appris par Léa visibles dans le VWB ("Appris par Léa")
- Bouton "Importer" pour éditer un workflow appris
- Bouton "Exporter pour Léa" pour rendre un workflow VWB exécutable
- Conversion bidirectionnelle core ↔ VWB via learned_workflow_bridge
- Liste unifiée dans le chat Léa (merged + dédupliquée)
- reload_workflows() sur le streaming server (pas de redémarrage)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -370,15 +370,22 @@ def api_status():
|
||||
|
||||
@app.route('/api/workflows')
|
||||
def api_workflows():
|
||||
"""Liste des workflows (tous répertoires confondus).
|
||||
"""Liste unifiée des workflows (appris + VWB).
|
||||
|
||||
Enrichit les workflows avec le machine_id source quand disponible
|
||||
(attribut _machine_id ajouté par le StreamProcessor).
|
||||
Sources fusionnées :
|
||||
1. Workflows appris (SemanticMatcher — data/training/workflows/)
|
||||
2. Workflows VWB (port 5002 — SQLite, édités par l'humain)
|
||||
|
||||
Dédupliqués par nom : si un workflow appris a été importé dans le VWB,
|
||||
seule la version VWB est retournée (c'est la version validée/corrigée).
|
||||
"""
|
||||
if not matcher:
|
||||
return jsonify({"workflows": [], "directories": []})
|
||||
|
||||
seen_ids = set()
|
||||
workflows = []
|
||||
|
||||
# Source 1 : workflows appris (core JSON)
|
||||
for wf in matcher.get_all_workflows():
|
||||
wf_data = {
|
||||
"id": wf.workflow_id,
|
||||
@@ -386,12 +393,31 @@ def api_workflows():
|
||||
"description": wf.description,
|
||||
"tags": wf.tags,
|
||||
"source": wf.source_dir,
|
||||
"origin": "learned",
|
||||
}
|
||||
# Ajouter le machine_id source si disponible (workflows appris en streaming)
|
||||
machine_id = getattr(wf, '_machine_id', None)
|
||||
if machine_id:
|
||||
wf_data["machine_id"] = machine_id
|
||||
workflows.append(wf_data)
|
||||
seen_ids.add(wf.workflow_id)
|
||||
|
||||
# Source 2 : workflows VWB (édités par l'humain)
|
||||
vwb_workflows = _fetch_vwb_workflows()
|
||||
for vwb_wf in vwb_workflows:
|
||||
vwb_id = vwb_wf.get("id", "")
|
||||
vwb_wf["origin"] = "vwb"
|
||||
# Si un workflow VWB a été importé depuis un appris, marquer le doublon
|
||||
desc = vwb_wf.get("description", "") or ""
|
||||
# Détecter les doublons par nom similaire ou description contenant l'ID core
|
||||
is_duplicate = False
|
||||
for core_id in seen_ids:
|
||||
if core_id in desc:
|
||||
is_duplicate = True
|
||||
break
|
||||
if vwb_id not in seen_ids and not is_duplicate:
|
||||
workflows.append(vwb_wf)
|
||||
seen_ids.add(vwb_id)
|
||||
|
||||
# Récupérer la liste des machines connectées depuis le streaming server
|
||||
machines = _fetch_connected_machines()
|
||||
@@ -403,6 +429,35 @@ def api_workflows():
|
||||
})
|
||||
|
||||
|
||||
def _fetch_vwb_workflows():
|
||||
"""Récupère les workflows depuis le VWB backend (port 5002)."""
|
||||
try:
|
||||
resp = http_requests.get(
|
||||
"http://localhost:5002/api/v3/session/state",
|
||||
timeout=3,
|
||||
)
|
||||
if resp.ok:
|
||||
data = resp.json()
|
||||
wf_list = data.get("workflows_list", [])
|
||||
result = []
|
||||
for wf in wf_list:
|
||||
result.append({
|
||||
"id": wf.get("id", ""),
|
||||
"name": wf.get("name", ""),
|
||||
"description": wf.get("description", ""),
|
||||
"tags": wf.get("tags", []),
|
||||
"source": "vwb",
|
||||
"step_count": wf.get("step_count", 0),
|
||||
"review_status": wf.get("review_status"),
|
||||
})
|
||||
return result
|
||||
except http_requests.ConnectionError:
|
||||
logger.debug("VWB backend (port 5002) indisponible")
|
||||
except Exception as e:
|
||||
logger.warning("Erreur récupération workflows VWB: %s", e)
|
||||
return []
|
||||
|
||||
|
||||
@app.route('/api/workflows/refresh', methods=['POST'])
|
||||
def api_workflows_refresh():
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user