feat(gui): n'afficher comme disponibles que les moteurs embarqués dans le build GUI

Axe application GUI (utilisateur final) : cohérence UI/moteurs propre au build
GUI, sans présumer du build CLI. EDS-Pseudo / GLiNER désactivés (switch disabled
+ « non embarqué dans cette version ») et `enable_eds/gliner` forcés à False quand
indisponibles ; CamemBERT-bio reste le moteur standard actif. Note Moteurs des
Profils rendue honnête. `_mini_toggle` gère `disabled`/`disabled_hint` + `.switch`.

2 tests GUI (toggles désactivés si indispo + état forcé False ; actifs si dispo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 17:38:56 +02:00
parent 890edb360e
commit 5e5f0bd341
2 changed files with 81 additions and 7 deletions

View File

@@ -15,6 +15,7 @@ from tkinter import filedialog, messagebox
import customtkinter as ctk
import engine_capabilities
from gui_v6 import theme as theme_mod
from gui_v6 import ui_kit
from gui_v6.config_state import ConfigState, default_profile_key, list_profile_keys
@@ -305,16 +306,26 @@ class ConfigTab(ctk.CTkFrame):
hint_row, text="Pourquoi pas tout coché ?", text_color=p["text_muted"], font=ui_kit.font(11), anchor="w"
).pack(side="left")
ui_kit.help_button(hint_row, p, _HELP_MOTEURS, title="Moteurs de détection").pack(side="right")
# Honnêteté moteurs : ne pas proposer un moteur que ce build n'embarque pas.
caps = engine_capabilities.capabilities_map()
eds_off = not caps["eds"].available
gli_off = not caps["gliner"].available
if eds_off:
self._state.enable_eds = False
if gli_off:
self._state.enable_gliner = False
self._tog_ner = self._mini_toggle(
ner, "CamemBERT-bio", "standard · rapide · F1 0.963", value=self._state.use_local_ner, command=self._on_ner
)
self._tog_ner.pack(fill="x", padx=12, pady=1)
self._tog_eds = self._mini_toggle(
ner, "EDS-Pseudo", "optionnel · médical français · plus lent", value=self._state.enable_eds, command=self._on_eds
ner, "EDS-Pseudo", "optionnel · médical français · plus lent", value=self._state.enable_eds,
command=self._on_eds, disabled=eds_off, disabled_hint="non embarqué dans cette version",
)
self._tog_eds.pack(fill="x", padx=12, pady=1)
self._tog_gli = self._mini_toggle(
ner, "GLiNER", "optionnel · vote croisé · plus lent", value=self._state.enable_gliner, command=self._on_gliner
ner, "GLiNER", "optionnel · vote croisé · plus lent", value=self._state.enable_gliner,
command=self._on_gliner, disabled=gli_off, disabled_hint="non embarqué dans cette version",
)
self._tog_gli.pack(fill="x", padx=12, pady=1)
self._mini_toggle(
@@ -512,7 +523,14 @@ class ConfigTab(ctk.CTkFrame):
self._pro_disable_vlm_var = ctk.BooleanVar(value=False)
self._pro_vlm_switch = ctk.CTkSwitch(eng, text="Désactiver le moteur VLM (images)", variable=self._pro_disable_vlm_var, progress_color=p["primary"], text_color=p["text"], font=ui_kit.font(12))
self._pro_vlm_switch.pack(anchor="w", padx=12, pady=(0, 6))
ctk.CTkLabel(eng, text="CamemBERT-bio (standard) toujours actif ; EDS-Pseudo / GLiNER optionnels.", text_color=p["text_muted"], font=ui_kit.font(11), anchor="w", wraplength=300, justify="left").pack(fill="x", padx=12, pady=(0, 12))
# Note honnête : reflète les moteurs réellement embarqués par ce build.
caps_pro = engine_capabilities.capabilities_map()
opt = [c.label.split(" (")[0] for c in (caps_pro["eds"], caps_pro["gliner"]) if c.available]
if opt:
moteurs_note = "CamemBERT-bio (standard) toujours actif ; " + " / ".join(opt) + " disponibles (optionnels)."
else:
moteurs_note = "CamemBERT-bio (standard) toujours actif ; EDS-Pseudo / GLiNER non embarqués dans cette version."
ctk.CTkLabel(eng, text=moteurs_note, text_color=p["text_muted"], font=ui_kit.font(11), anchor="w", wraplength=300, justify="left").pack(fill="x", padx=12, pady=(0, 12))
words = ui_kit.Card(right, p, title="📝 Mots du profil")
words.pack(fill="both", expand=True)
@@ -941,19 +959,30 @@ class ConfigTab(ctk.CTkFrame):
wraplength=330,
).pack(fill="x", padx=12, pady=(0, 10), ipady=5)
def _mini_toggle(self, parent, label: str, hint: str, value: bool = True, variable=None, command=None):
def _mini_toggle(self, parent, label: str, hint: str, value: bool = True, variable=None,
command=None, disabled: bool = False, disabled_hint: str | None = None):
p = self._p
row = ctk.CTkFrame(parent, fg_color="transparent", height=34)
row.pack_propagate(False)
left = ctk.CTkFrame(row, fg_color="transparent")
left.pack(side="left", fill="x", expand=True)
ctk.CTkLabel(left, text=label, text_color=p["text"], font=ui_kit.font(12), anchor="w").pack(anchor="w")
if hint:
ctk.CTkLabel(left, text=hint, text_color=p["text_muted"], font=ui_kit.font(10), anchor="w").pack(anchor="w")
lbl_color = p["text_muted"] if disabled else p["text"]
ctk.CTkLabel(left, text=label, text_color=lbl_color, font=ui_kit.font(12), anchor="w").pack(anchor="w")
shown_hint = disabled_hint if (disabled and disabled_hint) else hint
if shown_hint:
ctk.CTkLabel(left, text=shown_hint, text_color=p["text_muted"], font=ui_kit.font(10), anchor="w").pack(anchor="w")
# Moteur indisponible : on force l'état à False (jamais « coché mais absent »).
if disabled and variable is None:
value = False
var = variable if variable is not None else ctk.BooleanVar(value=value)
if disabled:
var.set(False)
switch = ctk.CTkSwitch(row, text="", variable=var, command=command, progress_color=p["primary"], width=38)
if disabled:
switch.configure(state="disabled")
switch.pack(side="right", padx=(6, 0))
row.var = var # type: ignore[attr-defined]
row.switch = switch # type: ignore[attr-defined]
row.get = lambda: bool(var.get()) # type: ignore[attr-defined]
return row