expAge commited on
Commit
8e780f6
·
1 Parent(s): f408a98

ui+ux: header epure / file preserve sur error / submit Drops / pending sobre / flush right

Browse files

5 ajustements demandes par l'utilisateur :

1. HEADER FILE EPURE — retire « (append-only, mis a jour a chaque
consolidation) » du header des .enrich envoyes a JDM. C'etait
du verbiage interne, JDM n'en a pas besoin. Header simple :
« # Soumission JeuxDeMots — fichier d'enrichissement. »

2. FILE PRESERVE SUR ERREUR — le yield d'erreur agent dans
run_jarvis_flow utilise maintenant _current_file_path() au lieu
de last_file_path. Quand l'API LLM crashe, le fichier de
consolidation (canonical_path peuple par auto-append) reste
affiche en bas de la page, en plus d'etre visible dans Drops.
L'utilisateur ne perd plus son travail en cas d'incident.

3. SUBMIT DANS DROPS — nouveau bouton « 📤 Soumettre ce fichier
a JDM (LLMDrops) » dans le sous-onglet Drops. Devient
interactive des qu'un fichier est selectionne ET qu'une cle
LLMDrops est dispo (input bandeau ou env). Reutilise
jarvis.submit_existing_file. Statut affiche apres l'upload
(success + nom serveur ou erreur). Permet de submettre un
fichier d'un run precedent sans relancer le flow.

4. PENDING LINE SOBRE HORS ENRICH — « ⏳ Generation en cours… »
reservait le compteur « (X consolides) » a l'enrichissement
mais affichait quand meme « (X consolides) » sans /Y sur les
autres flows (audit, gap, signalement, stats) — trompeur car
ces flows n'ont pas la semantique de consolidation. Maintenant
purement « ⏳ Generation en cours… » sans compteur quand
consolidation_target=None (= tous les flows hors enrich).

5. DROPS FLUSH A DROITE + RENAME — l'onglet « 📁 Productions »
devient « 📥 Drops » (nom plus court et plus parlant —
« les drops/soumissions JDM »). pushAideTabRight() etendu pour
accepter une liste de targets [« Aide », « Drops »] → applique
margin-left:auto a tous les tabs matching, peu importe leur
niveau (top-level pour Aide, sous-onglet Jarvis pour Drops).
Tests : 35/35 verts.

Files changed (3) hide show
  1. app.py +83 -7
  2. jarvis.py +14 -8
  3. src/jdm_agent/enrich/validators.py +1 -2
app.py CHANGED
@@ -2127,18 +2127,24 @@ _HEAD_JS = """
2127
  setTimeout(bindChatbotObserver, 800);
2128
  setTimeout(bindChatbotObserver, 2000);
2129
 
2130
- // ---------- Onglet Aide flush à droite ----------
2131
  // CSS pur ne marche pas de façon fiable (structure DOM Gradio v5
2132
  // varie). On cherche tous les boutons role="tab" qui contiennent
2133
- // « Aide » dans leur texte et on leur applique margin-left:auto.
 
 
2134
  function pushAideTabRight() {
2135
  var tabs = document.querySelectorAll('[role="tab"]');
2136
  var done = false;
 
2137
  for (var i = 0; i < tabs.length; i++) {
2138
  var label = (tabs[i].textContent || '').trim();
2139
- if (label.indexOf('Aide') >= 0) {
2140
- tabs[i].style.marginLeft = 'auto';
2141
- done = true;
 
 
 
2142
  }
2143
  }
2144
  return done;
@@ -3876,13 +3882,15 @@ with gr.Blocks(theme=THEME, title="JDMAgent Demo", head=_HEAD_JS, css=_CHATBOT_C
3876
  # viewer adaptatif à droite (iframe pour HTML, code pour text,
3877
  # télécharge pour le reste). Pas d'écrasement : tout file producer
3878
  # détecte les collisions et suffixe (_2, _3…).
3879
- with gr.Tab("📁 Productions", id="jarvis-productions"):
3880
  gr.Markdown(
3881
  "**Tous les fichiers produits** par l'agent (sous-graphes, "
3882
  "enrichissements, audits, signalements, stats) sont listés "
3883
  "ici, du PLUS RÉCENT au PLUS ANCIEN. Aucun écrasement : les "
3884
  "collisions de nom sont suffixées automatiquement (`_2`, `_3`…). "
3885
- "La liste se rafraîchit toute seule toutes les 3 secondes."
 
 
3886
  )
3887
 
3888
  def _scan_productions_choices():
@@ -3947,6 +3955,16 @@ with gr.Blocks(theme=THEME, title="JDMAgent Demo", head=_HEAD_JS, css=_CHATBOT_C
3947
  visible=False, label="📥 Télécharger ce fichier",
3948
  interactive=False,
3949
  )
 
 
 
 
 
 
 
 
 
 
3950
 
3951
  def _render_production_file(selected_path):
3952
  """Affiche le fichier selon son extension :
@@ -4034,6 +4052,64 @@ with gr.Blocks(theme=THEME, title="JDMAgent Demo", head=_HEAD_JS, css=_CHATBOT_C
4034
  prod_text_viewer, prod_download],
4035
  )
4036
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4037
  def _refresh_choices():
4038
  """Re-scanne PRODUCTIONS_DIR et met à jour les choices
4039
  du Dropdown. Préserve la sélection courante si le
 
2127
  setTimeout(bindChatbotObserver, 800);
2128
  setTimeout(bindChatbotObserver, 2000);
2129
 
2130
+ // ---------- Onglets « Aide » et « Drops » flush à droite ----------
2131
  // CSS pur ne marche pas de façon fiable (structure DOM Gradio v5
2132
  // varie). On cherche tous les boutons role="tab" qui contiennent
2133
+ // un des labels cibles (Aide en top-level, Drops en sous-onglet
2134
+ // Jarvis) et on leur applique margin-left:auto pour les pousser
2135
+ // au bout à droite de leur tablist parent.
2136
  function pushAideTabRight() {
2137
  var tabs = document.querySelectorAll('[role="tab"]');
2138
  var done = false;
2139
+ var targets = ['Aide', 'Drops'];
2140
  for (var i = 0; i < tabs.length; i++) {
2141
  var label = (tabs[i].textContent || '').trim();
2142
+ for (var t = 0; t < targets.length; t++) {
2143
+ if (label.indexOf(targets[t]) >= 0) {
2144
+ tabs[i].style.marginLeft = 'auto';
2145
+ done = true;
2146
+ break;
2147
+ }
2148
  }
2149
  }
2150
  return done;
 
3882
  # viewer adaptatif à droite (iframe pour HTML, code pour text,
3883
  # télécharge pour le reste). Pas d'écrasement : tout file producer
3884
  # détecte les collisions et suffixe (_2, _3…).
3885
+ with gr.Tab("📥 Drops", id="jarvis-drops"):
3886
  gr.Markdown(
3887
  "**Tous les fichiers produits** par l'agent (sous-graphes, "
3888
  "enrichissements, audits, signalements, stats) sont listés "
3889
  "ici, du PLUS RÉCENT au PLUS ANCIEN. Aucun écrasement : les "
3890
  "collisions de nom sont suffixées automatiquement (`_2`, `_3`…). "
3891
+ "La liste se rafraîchit toute seule toutes les 3 secondes. "
3892
+ "Sélectionne un fichier puis **📤 Soumettre à JDM** pour le "
3893
+ "pousser au LLMDrops directement depuis ici."
3894
  )
3895
 
3896
  def _scan_productions_choices():
 
3955
  visible=False, label="📥 Télécharger ce fichier",
3956
  interactive=False,
3957
  )
3958
+ # Bouton soumission JDM — actif si fichier
3959
+ # sélectionné ET clé LLMDrops dispo (env ou
3960
+ # input). Sinon dégrise pour signaler.
3961
+ with gr.Row():
3962
+ prod_submit_btn = gr.Button(
3963
+ "📤 Soumettre ce fichier à JDM (LLMDrops)",
3964
+ variant="primary",
3965
+ interactive=False,
3966
+ )
3967
+ prod_submit_status = gr.Markdown(visible=False)
3968
 
3969
  def _render_production_file(selected_path):
3970
  """Affiche le fichier selon son extension :
 
4052
  prod_text_viewer, prod_download],
4053
  )
4054
 
4055
+ def _toggle_submit_btn(selected_path, drops_key):
4056
+ """Active le bouton submit si fichier sélectionné
4057
+ ET (clé fournie dans le bandeau OU env active)."""
4058
+ from jarvis import has_drops_key as _hk
4059
+ ok = bool(selected_path) and _hk(drops_key)
4060
+ return gr.update(interactive=ok)
4061
+
4062
+ # Réactive à chaque changement de sélection OU de clé
4063
+ prod_file_dropdown.change(
4064
+ _toggle_submit_btn,
4065
+ inputs=[prod_file_dropdown, jarvis_drops_key],
4066
+ outputs=[prod_submit_btn],
4067
+ )
4068
+ jarvis_drops_key.change(
4069
+ _toggle_submit_btn,
4070
+ inputs=[prod_file_dropdown, jarvis_drops_key],
4071
+ outputs=[prod_submit_btn],
4072
+ )
4073
+
4074
+ def _submit_production_file(selected_path, drops_key, jarvis_model_v):
4075
+ """Upload le fichier sélectionné vers LLMDrops via
4076
+ jarvis.submit_existing_file (gère env override de
4077
+ JDM_DROPS_API_KEY le temps de l'appel)."""
4078
+ if not selected_path:
4079
+ return gr.update(visible=True,
4080
+ value="⚠️ Aucun fichier sélectionné.")
4081
+ try:
4082
+ from jarvis import submit_existing_file
4083
+ res = submit_existing_file(
4084
+ file_path=selected_path,
4085
+ drops_key=(drops_key or ""),
4086
+ model_name=(jarvis_model_v or "manual_submission"),
4087
+ )
4088
+ if isinstance(res, dict) and res.get("ok"):
4089
+ uploaded = res.get("uploaded_as") or Path(selected_path).name
4090
+ return gr.update(
4091
+ visible=True,
4092
+ value=f"✅ **Soumis avec succès** sous le nom "
4093
+ f"`{uploaded}`. Réponse serveur : "
4094
+ f"`{str(res.get('response') or '')[:200]}`",
4095
+ )
4096
+ err = (res or {}).get("error") if isinstance(res, dict) else str(res)
4097
+ return gr.update(
4098
+ visible=True,
4099
+ value=f"❌ **Échec de soumission** : {err}",
4100
+ )
4101
+ except Exception as e:
4102
+ return gr.update(
4103
+ visible=True,
4104
+ value=f"❌ Erreur soumission : {e}",
4105
+ )
4106
+
4107
+ prod_submit_btn.click(
4108
+ _submit_production_file,
4109
+ inputs=[prod_file_dropdown, jarvis_drops_key, jarvis_model],
4110
+ outputs=[prod_submit_status],
4111
+ )
4112
+
4113
  def _refresh_choices():
4114
  """Re-scanne PRODUCTIONS_DIR et met à jour les choices
4115
  du Dropdown. Préserve la sélection courante si le
jarvis.py CHANGED
@@ -1226,15 +1226,16 @@ def run_jarvis_flow(
1226
  from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
1227
 
1228
  def _pending_line() -> str:
1229
- """Ligne « génération en cours » avec compteur cumulatif des
1230
- triplets consolidés (lu depuis le registry global, donc
1231
- cumulatif sur tout le flow y compris les relances). Format :
1232
- « Génération en cours… (X/Y consolidés) » si target fixé,
1233
- sinon « Génération en cours… (X consolidés) »."""
1234
- n = count_consolidations()
1235
  if consolidation_target:
 
1236
  return f"*⏳ Génération en cours… ({n}/{consolidation_target} consolidés)*"
1237
- return f"*⏳ Génération en cours… ({n} consolidés)*"
1238
 
1239
  def _current_file_path() -> Optional[str]:
1240
  """Renvoie canonical_path dès qu'il existe sur disque (auto-append
@@ -1979,11 +1980,16 @@ def run_jarvis_flow(
1979
  f"({len(progress_full)})</summary>\n\n"
1980
  f"{(chr(10)*2).join(progress_full)}\n\n</details>"
1981
  )
 
 
 
 
 
1982
  yield (
1983
  [{"role": "user", "content": user_display},
1984
  {"role": "assistant",
1985
  "content": f"❌ Erreur agent : {e}" + err_block}],
1986
- last_file_path, _read_file_preview(last_file_path),
1987
  )
1988
  return
1989
 
 
1226
  from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
1227
 
1228
  def _pending_line() -> str:
1229
+ """Ligne « génération en cours ». Le compteur cumulatif des
1230
+ triplets consolidés est affiché UNIQUEMENT pour l'enrichissement
1231
+ (= seul flow consolidation_target est défini). Les autres
1232
+ flows (audit, gap, signalement, stats) n'ont pas la sémantique
1233
+ de « consolidation » on n'expose pas un compteur trompeur.
1234
+ """
1235
  if consolidation_target:
1236
+ n = count_consolidations()
1237
  return f"*⏳ Génération en cours… ({n}/{consolidation_target} consolidés)*"
1238
+ return "*⏳ Génération en cours…*"
1239
 
1240
  def _current_file_path() -> Optional[str]:
1241
  """Renvoie canonical_path dès qu'il existe sur disque (auto-append
 
1980
  f"({len(progress_full)})</summary>\n\n"
1981
  f"{(chr(10)*2).join(progress_full)}\n\n</details>"
1982
  )
1983
+ # PRÉSERVATION DU FICHIER SUR ERREUR : on yield
1984
+ # _current_file_path() (priorité canonical_path
1985
+ # si auto-append a écrit quelque chose) pour
1986
+ # que l'utilisateur garde son fichier de
1987
+ # consolidation même quand l'API LLM crashe.
1988
  yield (
1989
  [{"role": "user", "content": user_display},
1990
  {"role": "assistant",
1991
  "content": f"❌ Erreur agent : {e}" + err_block}],
1992
+ _current_file_path(), _read_file_preview(_current_file_path()),
1993
  )
1994
  return
1995
 
src/jdm_agent/enrich/validators.py CHANGED
@@ -177,8 +177,7 @@ def _append_consolidation_to_file(term: str, relation: str, target: str,
177
  with p.open("a", encoding="utf-8") as f:
178
  if write_header:
179
  f.write(
180
- "# Soumission JeuxDeMots — fichier d'enrichissement "
181
- "(append-only, mis à jour à chaque consolidation).\n"
182
  "# Format : terme | relation | cible | annotation < explication >\n\n"
183
  )
184
  _CONSOLIDATION_OUTPUT_HEADER_WRITTEN = True
 
177
  with p.open("a", encoding="utf-8") as f:
178
  if write_header:
179
  f.write(
180
+ "# Soumission JeuxDeMots — fichier d'enrichissement.\n"
 
181
  "# Format : terme | relation | cible | annotation < explication >\n\n"
182
  )
183
  _CONSOLIDATION_OUTPUT_HEADER_WRITTEN = True