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:
@@ -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
|
||||
|
||||
|
||||
@@ -295,3 +295,48 @@ def test_regles_moved_into_profils(ctk_root, tmp_path, monkeypatch):
|
||||
# le builder du sous-onglet séparé n'existe plus
|
||||
assert "rul" not in tab._panels
|
||||
tab.destroy()
|
||||
|
||||
|
||||
def test_unavailable_engines_disabled_in_reglages(ctk_root, tmp_path, monkeypatch):
|
||||
"""Honnêteté moteurs : EDS-Pseudo / GLiNER non embarqués → switch désactivé
|
||||
et état forcé à False ; CamemBERT-bio reste actif."""
|
||||
import engine_capabilities as ec
|
||||
from gui_v6.tabs import tab_config
|
||||
|
||||
monkeypatch.setattr(tab_config, "_app_base_dir", lambda: tmp_path)
|
||||
fake = {
|
||||
"camembert": ec.EngineCapability("camembert", "CamemBERT-bio (standard)", True, True, "ok"),
|
||||
"eds": ec.EngineCapability("eds", "EDS-Pseudo (optionnel)", False, False, "non embarqué dans cette version (manque : edsnlp, spacy)"),
|
||||
"gliner": ec.EngineCapability("gliner", "GLiNER (optionnel)", False, False, "non embarqué dans cette version (manque : gliner)"),
|
||||
}
|
||||
monkeypatch.setattr(tab_config.engine_capabilities, "capabilities_map", lambda probes=None: fake)
|
||||
|
||||
tab = tab_config.ConfigTab(ctk_root)
|
||||
tab.update_idletasks()
|
||||
|
||||
assert str(tab._tog_ner.switch.cget("state")) == "normal" # CamemBERT standard actif
|
||||
assert str(tab._tog_eds.switch.cget("state")) == "disabled"
|
||||
assert str(tab._tog_gli.switch.cget("state")) == "disabled"
|
||||
assert tab._state.enable_eds is False
|
||||
assert tab._state.enable_gliner is False
|
||||
tab.destroy()
|
||||
|
||||
|
||||
def test_available_engines_enabled_in_reglages(ctk_root, tmp_path, monkeypatch):
|
||||
"""Si les moteurs optionnels sont embarqués, leurs switches restent actifs."""
|
||||
import engine_capabilities as ec
|
||||
from gui_v6.tabs import tab_config
|
||||
|
||||
monkeypatch.setattr(tab_config, "_app_base_dir", lambda: tmp_path)
|
||||
fake = {
|
||||
"camembert": ec.EngineCapability("camembert", "CamemBERT-bio (standard)", True, True, "ok"),
|
||||
"eds": ec.EngineCapability("eds", "EDS-Pseudo (optionnel)", True, False, "edsnlp + spacy disponibles"),
|
||||
"gliner": ec.EngineCapability("gliner", "GLiNER (optionnel)", True, False, "gliner disponible"),
|
||||
}
|
||||
monkeypatch.setattr(tab_config.engine_capabilities, "capabilities_map", lambda probes=None: fake)
|
||||
|
||||
tab = tab_config.ConfigTab(ctk_root)
|
||||
tab.update_idletasks()
|
||||
assert str(tab._tog_eds.switch.cget("state")) == "normal"
|
||||
assert str(tab._tog_gli.switch.cget("state")) == "normal"
|
||||
tab.destroy()
|
||||
|
||||
Reference in New Issue
Block a user