feat(vwb): nom par défaut explicite pour workflows importés de Léa (B2)

Avant : tous les workflows importés s'appelaient « Unnamed Workflow »
→ la liste devenait illisible dès qu'il y en avait plusieurs.

Après : génération d'un nom explicite par _derive_default_name :
  1. Premier `template.window.title_pattern` utile dans les nodes
     (filtrage de "Unknown" / "unknown_window"), avec extraction de
     l'app derrière le séparateur Windows « – » / « - »
     (ex: « Sans titre – Bloc-notes » → « Bloc-notes »).
  2. Premier `template.window.process_name` non-null
     (ex: « explorer.exe »).
  3. Fallback : 8 premiers caractères du workflow_id, après
     nettoyage des préfixes techniques ("workflow_sess_", ...).

Le nom final inclut toujours la date de l'import :
    « Léa Bloc-notes — 2026-04-16 08:41 »
    « Léa explorer.exe — 2026-04-16 08:41 »
    « Léa 20260404 — 2026-04-16 08:41 » (fallback)

Ne se déclenche que si le nom entrant est vide,
« Unnamed Workflow » ou « Workflow importé » (insensible à la
casse). Le paramètre `name` explicite de la requête reste
prioritaire. L'utilisateur peut renommer via le bouton éditer.

Pas de modification du schema workflow (champ `name` existant).

Tests manuels sur données réelles :
- notepad_enriched.json (tous nodes "Unknown") → fallback id OK
- Bloc-notes, Explorateur et Recherche (2) → « Léa Rechercher »
- workflow construit avec title 'Sans titre – Bloc-notes'
  → « Léa Bloc-notes » OK

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-04-16 08:42:11 +02:00
parent 39bea1b042
commit 10739c33fa

View File

@@ -40,6 +40,71 @@ if _ROOT not in sys.path:
STREAMING_SERVER_URL = "http://localhost:5005" STREAMING_SERVER_URL = "http://localhost:5005"
# ---------------------------------------------------------------------------
# Helpers — nom par défaut à l'import
# ---------------------------------------------------------------------------
def _derive_default_name(core_dict: Dict[str, Any]) -> str:
"""
Génère un nom par défaut explicite pour un workflow appris importé,
quand son champ `name` est vide ou vaut « Unnamed Workflow ».
Stratégie, par ordre de priorité :
1. Premier `template.window.title_pattern` exploitable dans les nodes
(après filtrage de "Unknown"/"unknown_window") ; on extrait le nom
de l'app derrière un séparateur « » / « - » typique de Windows
(« Sans titre Bloc-notes » → « Bloc-notes »).
2. Premier `template.window.process_name` non-null.
3. Fallback : 8 premiers caractères de `workflow_id`.
La date de l'import (YYYY-MM-DD HH:MM) est toujours ajoutée en suffixe.
L'utilisateur peut renommer ensuite dans le VWB.
"""
from datetime import datetime as _dt
def _extract_app(title: str) -> Optional[str]:
if not title:
return None
t = title.strip()
if not t or t.lower() in {"unknown", "unknown_window"}:
return None
# Séparateurs Windows classiques : « » (em dash), « — », « - »
for sep in (" ", "", " - "):
if sep in t:
# Le nom de l'app est généralement la partie droite
right = t.rsplit(sep, 1)[-1].strip()
if right:
return right
# Pas de séparateur → renvoyer le titre brut (ex : "Rechercher")
return t
app_name: Optional[str] = None
for node in (core_dict.get("nodes") or []):
window = ((node.get("template") or {}).get("window") or {})
app_name = _extract_app(window.get("title_pattern") or "")
if app_name:
break
proc = window.get("process_name")
if proc:
app_name = str(proc).strip()
break
timestamp = _dt.now().strftime("%Y-%m-%d %H:%M")
if app_name:
return f"Léa {app_name}{timestamp}"
wf_id = core_dict.get("workflow_id") or ""
# Nettoyer les préfixes techniques courants (workflow_, sess_) pour garder
# un identifiant lisible de 8 caractères.
for prefix in ("workflow_sess_", "workflow_", "sess_", "session_"):
if wf_id.startswith(prefix):
wf_id = wf_id[len(prefix):]
break
suffix = wf_id[:8] if wf_id else "?"
return f"Léa {suffix}{timestamp}"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# GET /api/v3/learned-workflows # GET /api/v3/learned-workflows
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -209,7 +274,14 @@ def import_learned_workflow(workflow_id: str):
wf_meta, steps_list, warnings = convert_learned_to_vwb_steps(core_dict) wf_meta, steps_list, warnings = convert_learned_to_vwb_steps(core_dict)
# Surcharger le nom si fourni # B2 — nom par défaut explicite pour les workflows arrivant en
# "Unnamed Workflow" depuis Léa. N'affecte pas les workflows déjà
# nommés manuellement. L'humain peut renommer ensuite dans le VWB.
current_name = (wf_meta.get("name") or "").strip()
if current_name.lower() in {"", "unnamed workflow", "workflow importé"}:
wf_meta["name"] = _derive_default_name(core_dict)
# Surcharger le nom si fourni explicitement dans la requête
if data.get("name"): if data.get("name"):
wf_meta["name"] = data["name"] wf_meta["name"] = data["name"]