Backup état complet après enregistrement vidéo démo de bout en bout. À utiliser comme point de référence pour la consolidation post-démo. Changements majeurs de la session 18-19 mai : - AIVA-URGENCE : page autonome avec preset URL + auto-focus chain - Workflow Demo_urgence_3_db : merge linux_db + steps AIVA + pause humaine NoMachine - Bypass LLM (static_result / static_text) dans replay_engine pour démos déterministes sans appel Ollama - Fix api_stream:3013 — replay_paused au premier polling /next - dag_execute : lift duration_ms vers top-level pour wait runtime - NPM bypass auth /aiva-urgence/ via location ^~ (proxy_host/10.conf hors git) - scripts/cancel-replays.sh — workaround Stop VWB qui ne purge pas la queue Anchors visuels (468) forcés dans le commit pour garantir restorabilité. DB workflows actuelle + ~12 .bak DB de la journée incluses. Sujets identifiés pour consolidation post-démo (TODO) : 1. Bug VWB recapture anchor ne régénère pas le PNG 2. Léa client accumule état mémoire (restart périodique requis) 3. Stop VWB ne purge pas la queue serveur (lien manquant vers /replay/cancel) 4. Bug coord client mss tronqué 2560x60 → mapping Y cassé 5. delay_before/delay_after ignorés au runtime (fix partiel duration_ms) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
165 lines
5.4 KiB
Python
165 lines
5.4 KiB
Python
"""Duplique le workflow Demo_urgence_2 en Demo_urgence_2_interop.
|
|
|
|
- Source : wf_d04d2dc7c118_1778493082
|
|
- Exclus : ord 13, 15, 16, 18, 19 (steps UI Codage Easily)
|
|
- Conservés : ord 0-12, 14, 17 → renumérotés 0..14
|
|
- Anchors partagés (pas de duplication de visual_anchors)
|
|
- Transaction SQLite : commit unique en fin.
|
|
|
|
Usage :
|
|
python tools/duplicate_demo_urgence_2_interop.py [--dry-run]
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import secrets
|
|
import sqlite3
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
DB_PATH = Path(__file__).resolve().parent.parent / "visual_workflow_builder" / "backend" / "instance" / "workflows.db"
|
|
SOURCE_WF_ID = "wf_d04d2dc7c118_1778493082"
|
|
NEW_WF_NAME = "Demo_urgence_2_interop"
|
|
ORDS_TO_EXCLUDE = {13, 15, 16, 18, 19}
|
|
|
|
|
|
def new_id(prefix: str, ts: int) -> str:
|
|
return f"{prefix}_{secrets.token_hex(6)}_{ts}"
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--dry-run", action="store_true", help="Pas de COMMIT, juste afficher.")
|
|
args = parser.parse_args()
|
|
|
|
if not DB_PATH.exists():
|
|
print(f"ERREUR : DB introuvable {DB_PATH}", file=sys.stderr)
|
|
return 1
|
|
|
|
ts = int(time.time())
|
|
new_wf_id = new_id("wf", ts)
|
|
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.row_factory = sqlite3.Row
|
|
cur = conn.cursor()
|
|
|
|
# 1. Vérifier que le nom de destination n'existe pas déjà
|
|
row = cur.execute("SELECT id FROM workflows WHERE name = ?", (NEW_WF_NAME,)).fetchone()
|
|
if row:
|
|
print(f"ERREUR : un workflow nommé '{NEW_WF_NAME}' existe déjà (id={row['id']})", file=sys.stderr)
|
|
return 2
|
|
|
|
# 2. Lire la ligne workflow source
|
|
src_wf = cur.execute("SELECT * FROM workflows WHERE id = ?", (SOURCE_WF_ID,)).fetchone()
|
|
if not src_wf:
|
|
print(f"ERREUR : workflow source {SOURCE_WF_ID} introuvable", file=sys.stderr)
|
|
return 3
|
|
|
|
# 3. Lire les steps à conserver, dans l'ordre
|
|
src_steps = cur.execute(
|
|
'SELECT * FROM steps WHERE workflow_id = ? ORDER BY "order"',
|
|
(SOURCE_WF_ID,),
|
|
).fetchall()
|
|
kept_steps = [s for s in src_steps if s["order"] not in ORDS_TO_EXCLUDE]
|
|
if len(kept_steps) != 15:
|
|
print(f"ERREUR : attendu 15 steps conservés, obtenu {len(kept_steps)}", file=sys.stderr)
|
|
return 4
|
|
|
|
# 4. Préparer mapping (renumérotation 0..14)
|
|
mapping = []
|
|
for new_order, s in enumerate(kept_steps):
|
|
new_step_id = new_id("step", ts + new_order) # ts unique par step
|
|
mapping.append({
|
|
"old_id": s["id"],
|
|
"new_id": new_step_id,
|
|
"old_order": s["order"],
|
|
"new_order": new_order,
|
|
"action_type": s["action_type"],
|
|
"label": s["label"],
|
|
"position_x": s["position_x"],
|
|
"position_y": s["position_y"],
|
|
"parameters_json": s["parameters_json"],
|
|
"anchor_id": s["anchor_id"],
|
|
})
|
|
|
|
# 5. Affichage tableau avant/après
|
|
print(f"\nWorkflow source : {SOURCE_WF_ID} (name={src_wf['name']})")
|
|
print(f"Workflow cible : {new_wf_id} (name={NEW_WF_NAME})")
|
|
print(f"Steps conservés : {len(mapping)} / {len(src_steps)}")
|
|
print(f"\n{'old_ord':>7} → {'new_ord':>7} {'action_type':<20} label")
|
|
print("-" * 80)
|
|
for m in mapping:
|
|
print(f"{m['old_order']:>7} → {m['new_order']:>7} {m['action_type']:<20} {m['label']}")
|
|
print()
|
|
|
|
if args.dry_run:
|
|
print("--dry-run : aucune modification de la DB.")
|
|
return 0
|
|
|
|
# 6. Exécution transactionnelle
|
|
now_iso = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
try:
|
|
cur.execute("BEGIN")
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO workflows
|
|
(id, name, description, tags_json, trigger_examples_json,
|
|
created_at, updated_at, is_active, source,
|
|
review_status, review_feedback, reviewed_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
(
|
|
new_wf_id,
|
|
NEW_WF_NAME,
|
|
src_wf["description"],
|
|
src_wf["tags_json"],
|
|
src_wf["trigger_examples_json"],
|
|
now_iso,
|
|
now_iso,
|
|
src_wf["is_active"],
|
|
src_wf["source"],
|
|
src_wf["review_status"],
|
|
src_wf["review_feedback"],
|
|
src_wf["reviewed_at"],
|
|
),
|
|
)
|
|
|
|
for m in mapping:
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO steps
|
|
(id, workflow_id, action_type, "order",
|
|
position_x, position_y, parameters_json, anchor_id, label,
|
|
created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
(
|
|
m["new_id"],
|
|
new_wf_id,
|
|
m["action_type"],
|
|
m["new_order"],
|
|
m["position_x"],
|
|
m["position_y"],
|
|
m["parameters_json"],
|
|
m["anchor_id"],
|
|
m["label"],
|
|
now_iso,
|
|
now_iso,
|
|
),
|
|
)
|
|
|
|
conn.commit()
|
|
print(f"OK — workflow {NEW_WF_NAME} créé ({len(mapping)} steps), id={new_wf_id}")
|
|
return 0
|
|
except Exception as e:
|
|
conn.rollback()
|
|
print(f"ROLLBACK — exception : {e}", file=sys.stderr)
|
|
return 5
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|