feat(gui): GUI V6 G3 — câblage moteur, Configuration, licence UI, build-prep
G3-A câblage moteur réel (engine_bridge.py) : EngineSettings + NerManagers à chargement paresseux (aucun manager à l'import), kwargs alignés CLI/V5 (make_vector_redaction=False, also_make_raster_burn=True, config_path, use_hf, ner/gliner/camembert_manager, ogc_label) ; make_process_fn engine injectable ; état managers not_loaded/loading/ready/unavailable, échecs optionnels tolérés. G3-B Configuration (config_state.py + tabs/tab_config.py) : ConfigState → EngineSettings, profils via profile_defaults (path injectable), options raster/NER local/profil/sortie, état managers, sections admin-only via admin_mode. G3-C Licence UI (machine_id.py + tab_about) : activation par clef (LicenseClient.activate), bouton vérifier (check), affichage statut, aucun token loggé, aucun appel réseau au démarrage (local_status seul). Intégration : tab_usage exécute via le moteur réel selon ConfigState (make_process_fn), anti double-lancement UI. app.py câble Config↔Usage↔licence. G3-D build-prep : anonymisation_gui_v6_onefile.spec (entry V6, customtkinter + modules gui_v6 en hiddenimports). Installateur Anonymisation.iss produit déjà la cible Anonymisation-Setup.exe. Aucun artefact .exe commité ; build Windows à part. Tests +14 (engine_bridge 8, config_state 6). self-test exit 0, 46 tests gui_v6, 193 tests/unit (0 régression). Moteur/V5/specs CLI intacts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
128
gui_v6/tabs/tab_config.py
Normal file
128
gui_v6/tabs/tab_config.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""Onglet « Configuration » de la GUI V6 (G3-B).
|
||||
|
||||
Édite un :class:`ConfigState` partagé : profil métier, raster burn, NER local,
|
||||
dossier de sortie. Affiche l'état des managers NER. Les options sensibles ne sont
|
||||
visibles/éditables qu'en mode admin (``admin_mode.is_admin``). Aucune logique de
|
||||
détection : on édite seulement l'état lu par l'onglet Utilisation.
|
||||
|
||||
Les widgets ne sont créés qu'à l'instanciation (import sûr pour ``--self-test``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from tkinter import filedialog
|
||||
|
||||
import customtkinter as ctk
|
||||
|
||||
from gui_v6.config_state import ConfigState, default_profile_key, list_profile_keys
|
||||
|
||||
|
||||
def _is_admin() -> bool:
|
||||
try:
|
||||
from admin_mode import is_admin
|
||||
|
||||
return bool(is_admin())
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class ConfigTab(ctk.CTkFrame):
|
||||
def __init__(self, master, state: ConfigState | None = None, **kwargs):
|
||||
super().__init__(master, **kwargs)
|
||||
self._state = state if state is not None else ConfigState()
|
||||
self._admin = _is_admin()
|
||||
self._build()
|
||||
|
||||
@property
|
||||
def state(self) -> ConfigState:
|
||||
return self._state
|
||||
|
||||
def _build(self) -> None:
|
||||
ctk.CTkLabel(
|
||||
self, text="Configuration", font=ctk.CTkFont(size=16, weight="bold")
|
||||
).pack(anchor="w", padx=16, pady=(16, 8))
|
||||
|
||||
# Profil métier
|
||||
profiles = list_profile_keys()
|
||||
current = self._state.profile or default_profile_key() or (profiles[0] if profiles else "")
|
||||
self._state.profile = current or None
|
||||
row = ctk.CTkFrame(self)
|
||||
row.pack(fill="x", padx=16, pady=4)
|
||||
ctk.CTkLabel(row, text="Profil :").pack(side="left", padx=(0, 8))
|
||||
self._profile_menu = ctk.CTkOptionMenu(
|
||||
row,
|
||||
values=profiles or ["(aucun profil)"],
|
||||
command=self._on_profile,
|
||||
)
|
||||
if current:
|
||||
self._profile_menu.set(current)
|
||||
self._profile_menu.pack(side="left")
|
||||
|
||||
# Options simples
|
||||
self._raster = ctk.CTkCheckBox(
|
||||
self, text="Caviardage raster (burn)", command=self._on_raster
|
||||
)
|
||||
self._raster.select() if self._state.raster_burn else self._raster.deselect()
|
||||
self._raster.pack(anchor="w", padx=16, pady=4)
|
||||
|
||||
self._ner = ctk.CTkCheckBox(
|
||||
self, text="NER local actif", command=self._on_ner
|
||||
)
|
||||
self._ner.select() if self._state.use_local_ner else self._ner.deselect()
|
||||
self._ner.pack(anchor="w", padx=16, pady=4)
|
||||
|
||||
# Dossier de sortie
|
||||
out_row = ctk.CTkFrame(self)
|
||||
out_row.pack(fill="x", padx=16, pady=4)
|
||||
ctk.CTkButton(out_row, text="Dossier de sortie…", command=self._pick_output).pack(
|
||||
side="left", padx=(0, 8)
|
||||
)
|
||||
self._output_label = ctk.CTkLabel(
|
||||
out_row, text=str(self._state.output_dir or "(défaut anonymise/)")
|
||||
)
|
||||
self._output_label.pack(side="left")
|
||||
|
||||
# Options admin-only
|
||||
if self._admin:
|
||||
ctk.CTkLabel(self, text="Options avancées (admin)").pack(
|
||||
anchor="w", padx=16, pady=(12, 4)
|
||||
)
|
||||
self._gliner = ctk.CTkCheckBox(
|
||||
self, text="GLiNER (vote croisé)", command=self._on_gliner
|
||||
)
|
||||
self._gliner.pack(anchor="w", padx=16, pady=2)
|
||||
self._eds = ctk.CTkCheckBox(
|
||||
self, text="EDS-Pseudo", command=self._on_eds
|
||||
)
|
||||
self._eds.pack(anchor="w", padx=16, pady=2)
|
||||
|
||||
# État des managers
|
||||
self._managers_label = ctk.CTkLabel(self, text="Managers NER : non chargé", anchor="w")
|
||||
self._managers_label.pack(anchor="w", padx=16, pady=(12, 16))
|
||||
|
||||
# -- callbacks --------------------------------------------------------
|
||||
|
||||
def _on_profile(self, value: str) -> None:
|
||||
self._state.profile = value
|
||||
|
||||
def _on_raster(self) -> None:
|
||||
self._state.raster_burn = bool(self._raster.get())
|
||||
|
||||
def _on_ner(self) -> None:
|
||||
self._state.use_local_ner = bool(self._ner.get())
|
||||
|
||||
def _on_gliner(self) -> None:
|
||||
self._state.enable_gliner = bool(self._gliner.get())
|
||||
|
||||
def _on_eds(self) -> None:
|
||||
self._state.enable_eds = bool(self._eds.get())
|
||||
|
||||
def _pick_output(self) -> None:
|
||||
path = filedialog.askdirectory(title="Dossier de sortie")
|
||||
if path:
|
||||
self._state.output_dir = Path(path)
|
||||
self._output_label.configure(text=str(self._state.output_dir))
|
||||
|
||||
def set_managers_state(self, state_text: str) -> None:
|
||||
self._managers_label.configure(text=f"Managers NER : {state_text}")
|
||||
Reference in New Issue
Block a user