Améliorations GUI

This commit is contained in:
2026-08-22 11:55:17 +02:00
parent ce9f385a0a
commit 9af05f2c86
3 changed files with 205 additions and 20 deletions
+160 -18
View File
@@ -8,7 +8,7 @@ from tkinter import filedialog, messagebox, ttk
from typing import Any
from copienator import ExitCode
from platform_utils import WindowsLabelError, validate_windows_labels
from platform_utils import WindowsLabelError, open_path, validate_windows_labels
from .diagnostics import collect_diagnostics
from .runner import ProcessRunner
@@ -35,6 +35,65 @@ STATUS_LABELS = {
"detected": "Détectée",
}
DEFAULT_HTTPS_PROXY = "http://10.0.0.1:3128"
def copy_pdf_paths(evaluation: Path) -> list[Path]:
"""List the most relevant version of each scanned copy."""
locations = (evaluation / "Copies", evaluation, evaluation / "Copies Originales")
for location in locations:
if not location.is_dir():
continue
copies = sorted(
(
path
for path in location.glob("*.pdf")
if path.name.casefold() not in {"enonce.pdf", "énoncé.pdf"}
),
key=lambda path: path.name.casefold(),
)
if copies:
return copies
return []
def plotting_shortcut_lines() -> list[str]:
try:
from config import PLOTTING_KB
except (ImportError, AttributeError):
from default_config import PLOTTING_KB
labels = (
("OK", "valider et passer à la suivante"),
("previous", "revenir à la précédente"),
("edit", "éditer le fichier JSON"),
("open pdf", "ouvrir la copie traitée"),
("open original pdf", "ouvrir la copie originale"),
("open eval", "ouvrir l’énoncé"),
)
display_names = {"<Return>": "Entrée", "<Escape>": "Échap"}
lines = [
f"{display_names.get(PLOTTING_KB[action], PLOTTING_KB[action])} : {description}"
for action, description in labels
]
lines.append("Échap : fermer la fenêtre")
return lines
def build_runner_environment(
base: dict[str, str], api_key: str, proxy: str, use_proxy: bool
) -> dict[str, str]:
environment = dict(base)
environment["PYTHONUNBUFFERED"] = "1"
environment["PYTHONIOENCODING"] = "utf-8"
if api_key.strip():
environment["GEMINI_API_KEY"] = api_key.strip()
environment.pop("HTTPS_PROXY", None)
environment.pop("https_proxy", None)
if use_proxy and proxy.strip():
environment["HTTPS_PROXY"] = proxy.strip()
return environment
def process_status(return_code: int, interrupted: bool = False) -> str:
if interrupted or return_code == ExitCode.INTERRUPTED:
@@ -76,6 +135,7 @@ class CopienatorApp(tk.Tk):
self.active_step_id: str | None = None
self.current_step: StepDefinition | None = None
self.arg_vars: dict[str, tk.Variable] = {}
self.copy_paths: dict[str, Path] = {}
self._rendering = False
self.title("Copienator — assistant de correction")
@@ -85,7 +145,9 @@ class CopienatorApp(tk.Tk):
self.evaluation_var = tk.StringVar()
self.api_key_var = tk.StringVar(value=os.environ.get("GEMINI_API_KEY", ""))
self.proxy_var = tk.StringVar(value=os.environ.get("HTTPS_PROXY", ""))
self.proxy_var = tk.StringVar(value=DEFAULT_HTTPS_PROXY)
self.use_proxy_var = tk.BooleanVar(value=False)
self.copy_var = tk.StringVar()
self.variant_var = tk.StringVar()
self.extra_var = tk.StringVar()
self.command_var = tk.StringVar(value="Sélectionnez une étape.")
@@ -113,13 +175,17 @@ class CopienatorApp(tk.Tk):
top = ttk.Frame(self, padding=(10, 8))
top.grid(row=0, column=0, sticky="ew")
top.columnconfigure(1, weight=1)
ttk.Label(top, text="Évaluation").grid(row=0, column=0, sticky="w", padx=(0, 8))
top.columnconfigure(2, weight=1)
ttk.Button(top, text="Charger", command=self._load_evaluation).grid(
row=0, column=0, padx=(0, 8)
)
ttk.Label(top, text="Évaluation").grid(row=0, column=1, sticky="w", padx=(0, 8))
path_entry = ttk.Entry(top, textvariable=self.evaluation_var)
path_entry.grid(row=0, column=1, sticky="ew")
path_entry.grid(row=0, column=2, sticky="ew")
path_entry.bind("<Return>", lambda _event: self._load_evaluation())
ttk.Button(top, text="Parcourir…", command=self._browse_evaluation).grid(row=0, column=2, padx=6)
ttk.Button(top, text="Charger", command=self._load_evaluation).grid(row=0, column=3)
ttk.Button(top, text="Parcourir…", command=self._browse_evaluation).grid(
row=0, column=3, padx=6
)
ttk.Button(top, text="Diagnostic…", command=self._show_diagnostics).grid(row=0, column=4, padx=(6, 0))
ttk.Label(top, text="Clé Gemini").grid(row=1, column=0, sticky="w", padx=(0, 8), pady=(7, 0))
@@ -127,9 +193,15 @@ class CopienatorApp(tk.Tk):
row=1, column=1, sticky="w", pady=(7, 0)
)
environment = ttk.Frame(top)
environment.grid(row=1, column=2, columnspan=2, sticky="e", pady=(7, 0))
ttk.Label(environment, text="HTTPS_PROXY").pack(side="left", padx=(0, 6))
ttk.Entry(environment, textvariable=self.proxy_var, width=28).pack(side="left")
environment.grid(row=1, column=2, columnspan=3, sticky="e", pady=(7, 0))
ttk.Checkbutton(
environment,
text="Utiliser le proxy HTTPS",
variable=self.use_proxy_var,
command=self._toggle_proxy,
).pack(side="left", padx=(0, 6))
self.proxy_entry = ttk.Entry(environment, textvariable=self.proxy_var, width=28, state="disabled")
self.proxy_entry.pack(side="left")
profile = "standard + personnel" if show_personal_steps else "standard"
ttk.Label(environment, text=f"Profil : {profile}").pack(side="left", padx=(12, 0))
@@ -269,6 +341,9 @@ class CopienatorApp(tk.Tk):
self.evaluation_var.set(selected)
self._load_evaluation()
def _toggle_proxy(self) -> None:
self.proxy_entry.configure(state="normal" if self.use_proxy_var.get() else "disabled")
def _load_evaluation(self) -> None:
evaluation = self.evaluation
if not evaluation or not evaluation.is_dir():
@@ -437,7 +512,7 @@ class CopienatorApp(tk.Tk):
description += "\nPrérequis non détectés : " + ", ".join(missing)
self.description_label.configure(text=description)
row = 0
row = self._render_context_controls(step, 0)
if len(step.variants) > 1:
ttk.Label(self.form, text="Mode").grid(row=row, column=0, sticky="w", pady=4, padx=(0, 8))
labels = [variant.label for variant in step.variants]
@@ -499,6 +574,74 @@ class CopienatorApp(tk.Tk):
self._update_command_preview()
self._update_controls()
def _render_context_controls(self, step: StepDefinition, row: int) -> int:
if step.id == "review_persp":
ttk.Button(
self.form,
text="Ouvrir le dossier Persp",
command=self._open_persp,
).grid(row=row, column=0, columnspan=2, sticky="w", pady=(0, 8))
row += 1
if step.section == "Prétraitement des copies":
evaluation = self.evaluation
paths = copy_pdf_paths(evaluation) if evaluation else []
self.copy_paths = {path.name: path for path in paths}
names = list(self.copy_paths)
copies = ttk.LabelFrame(self.form, text="Copies détectées", padding=7)
copies.grid(row=row, column=0, columnspan=2, sticky="ew", pady=(0, 8))
copies.columnconfigure(0, weight=1)
summary = (
f"{len(names)} copie(s) : {', '.join(names)}"
if names
else "Aucune copie PDF détectée."
)
ttk.Label(copies, text=summary, wraplength=570, justify="left").grid(
row=0, column=0, columnspan=2, sticky="ew"
)
self.copy_var.set(names[0] if names else "")
selector = ttk.Combobox(
copies,
textvariable=self.copy_var,
values=names,
state="readonly" if names else "disabled",
)
selector.grid(row=1, column=0, sticky="ew", pady=(7, 0))
ttk.Button(
copies,
text="Afficher la copie",
command=self._open_selected_copy,
state="normal" if names else "disabled",
).grid(row=1, column=1, padx=(6, 0), pady=(7, 0))
row += 1
if step.id == "plotting":
shortcuts = ttk.LabelFrame(self.form, text="Raccourcis clavier", padding=7)
shortcuts.grid(row=row, column=0, columnspan=2, sticky="ew", pady=(0, 8))
ttk.Label(
shortcuts,
text="\n".join(plotting_shortcut_lines()),
justify="left",
).grid(row=0, column=0, sticky="w")
row += 1
return row
def _open_persp(self) -> None:
evaluation = self.evaluation
self._open_desktop_path(evaluation / "Persp" if evaluation else None, "dossier Persp")
def _open_selected_copy(self) -> None:
self._open_desktop_path(self.copy_paths.get(self.copy_var.get()), "copie")
def _open_desktop_path(self, path: Path | None, label: str) -> None:
if path is None or not path.exists():
messagebox.showerror("Élément introuvable", f"Le {label} nexiste pas encore.")
return
try:
open_path(path)
except (OSError, RuntimeError) as exc:
messagebox.showerror("Ouverture impossible", str(exc))
def _select_variant(self, index: int) -> None:
if not self.current_step or index < 0:
return
@@ -640,13 +783,12 @@ class CopienatorApp(tk.Tk):
assert workspace is not None
log_path = workspace.log_path(step.id)
environment = os.environ.copy()
environment["PYTHONUNBUFFERED"] = "1"
environment["PYTHONIOENCODING"] = "utf-8"
if self.api_key_var.get().strip():
environment["GEMINI_API_KEY"] = self.api_key_var.get().strip()
if self.proxy_var.get().strip():
environment["HTTPS_PROXY"] = self.proxy_var.get().strip()
environment = build_runner_environment(
os.environ,
self.api_key_var.get(),
self.proxy_var.get(),
self.use_proxy_var.get(),
)
self._append_console(f"\n$ {command_display(command)}\n")
try:
+1 -1
View File
@@ -109,7 +109,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
StepDefinition(
"review_persp",
"Prétraitement de l’énoncé",
"Relire les barèmes dans Persp",
"Relire les barèmes",
"Étape manuelle facultative : vérifier et modifier les instructions de correction.",
(manual("review"),),
optional=True,