Miscs personal GUI improvement

This commit is contained in:
2026-09-14 09:42:13 +02:00
parent e8b8a11c5b
commit d60d5479d6
5 changed files with 207 additions and 33 deletions
+97 -24
View File
@@ -2,6 +2,8 @@ from __future__ import annotations
import os
import queue
import re
import shutil
import tkinter as tk
from dataclasses import replace
from pathlib import Path
@@ -10,6 +12,7 @@ from typing import Any
from copienator import CliError, EvaluationWorkspace, ExitCode, atomic_write_json
from copienator.copy_errors import copy_errors
from copienator.filesystem import staged_files
from copienator.platform import (
WindowsLabelError,
add_platform_executable_paths,
@@ -52,6 +55,7 @@ STATUS_LABELS = {
}
DEFAULT_HTTPS_PROXY = "http://10.0.0.1:3128"
PERSONAL_INTERRO_SOURCE = Path("/home/sebastien/Prépa/Staging/Interro")
ANNOTATION_DIRECTORIES = ("BGnot", "Bnot", "Anot")
ANNOTATION_VARIANT_DIRECTORIES = {
"simple": "Anot",
@@ -60,6 +64,33 @@ ANNOTATION_VARIANT_DIRECTORIES = {
}
def get_personal_interro_files(
evaluation: Path, source_directory: Path = PERSONAL_INTERRO_SOURCE
) -> tuple[Path, Path, Path]:
"""Copy an Interro project's statement sources into the evaluation."""
match = re.fullmatch(r"Interro(\d+)", evaluation.name)
if match is None:
raise ValueError("Le dossier d’évaluation doit sappeler Interro{id}, avec un identifiant numérique.")
project_name = evaluation.name
source_to_destination = (
(source_directory / f"{project_name}.pdf", "enonce.pdf"),
(source_directory / f"{project_name}.tex", "enonce.tex"),
(source_directory / f"{project_name}c.tex", "correction.tex"),
)
missing = [source.name for source, _destination in source_to_destination if not source.is_file()]
if missing:
raise FileNotFoundError(
"Fichier(s) source introuvable(s) dans "
f"{source_directory} : {', '.join(missing)}"
)
with staged_files(evaluation) as staging:
for source, destination_name in source_to_destination:
shutil.copy2(source, staging / destination_name)
return tuple(evaluation / name for _source, name in source_to_destination)
class Tooltip:
"""Small delayed tooltip for Tk and ttk widgets."""
@@ -338,13 +369,23 @@ class CopienatorApp(tk.Tk):
self.detail = ttk.Frame(main_pane, padding=(12, 6, 4, 4))
self.detail.columnconfigure(0, weight=1)
self.detail.rowconfigure(2, weight=1)
self.detail.rowconfigure(3, weight=1)
self.title_label = ttk.Label(self.detail, text="Sélectionnez une étape", font=("TkDefaultFont", 15, "bold"))
self.title_label.grid(row=0, column=0, sticky="w")
self.description_label = ttk.Label(self.detail, text="", wraplength=680, justify="left")
self.description_label.grid(row=1, column=0, sticky="ew", pady=(5, 8))
ttk.Style(self).configure("MissingPrerequisite.TLabel", foreground="#c62828")
self.missing_requirements_label = ttk.Label(
self.detail,
text="",
style="MissingPrerequisite.TLabel",
wraplength=680,
justify="left",
)
self.missing_requirements_label.grid(row=2, column=0, sticky="ew", pady=(0, 8))
self.missing_requirements_label.grid_remove()
form_container = ttk.Frame(self.detail)
form_container.grid(row=2, column=0, sticky="nsew")
form_container.grid(row=3, column=0, sticky="nsew")
form_container.columnconfigure(0, weight=1)
form_container.rowconfigure(0, weight=1)
self.form_canvas = tk.Canvas(form_container, highlightthickness=0, width=1, height=1)
@@ -359,7 +400,7 @@ class CopienatorApp(tk.Tk):
self.form.columnconfigure(1, weight=1)
command_box = ttk.LabelFrame(self.detail, text="Commande", padding=6)
command_box.grid(row=3, column=0, sticky="ew", pady=(8, 5))
command_box.grid(row=4, column=0, sticky="ew", pady=(8, 5))
command_box.columnconfigure(0, weight=1)
ttk.Label(command_box, textvariable=self.command_var, wraplength=690, justify="left").grid(
row=0, column=0, sticky="ew"
@@ -368,7 +409,7 @@ class CopienatorApp(tk.Tk):
self.copy_command_button.grid(row=1, column=0, sticky="w", pady=(5, 0))
buttons = ttk.Frame(self.detail)
buttons.grid(row=4, column=0, sticky="ew", pady=(5, 0))
buttons.grid(row=5, column=0, sticky="ew", pady=(5, 0))
self.previous_button = ttk.Button(buttons, text="← Précédente", command=lambda: self._move_selection(-1))
self.previous_button.pack(side="left")
self.next_button = ttk.Button(buttons, text="Suivante →", command=lambda: self._move_selection(1))
@@ -603,6 +644,40 @@ class CopienatorApp(tk.Tk):
self._render_step()
self.info_var.set("Fichiers revérifiés — " + ("manquants : " + ", ".join(missing) if missing else "tous les fichiers dentrée sont présents."))
def _get_input_files(self) -> None:
evaluation = self.evaluation
if (
not self.show_personal_steps
or not evaluation
or not self.state_store.evaluation
or self.active_step_id
or self.runner.running
):
return
try:
copied = get_personal_interro_files(evaluation)
except (OSError, ValueError) as exc:
messagebox.showerror("Récupération impossible", str(exc))
return
missing = self._missing_requirements(self.step_by_id["inputs"])
if missing:
self.state_store.update_step("inputs", status="ready")
self._populate_tree()
self._render_step()
messagebox.showwarning(
"Prérequis encore manquant",
"Les trois fichiers ont été copiés, mais l’étape ne peut pas encore "
"être terminée :\n\n" + "\n".join(missing),
)
return
self._mark_step("success", automatic=True, reason="Fichiers dentrée récupérés")
self.info_var.set(
"Fichiers récupérés : " + ", ".join(path.name for path in copied)
)
self._move_selection_from("inputs", 1)
def _on_tree_select(self, _event: tk.Event[Any] | None = None) -> None:
selection = self.tree.selection()
if not selection or selection[0].startswith("section:"):
@@ -692,10 +767,15 @@ class CopienatorApp(tk.Tk):
if step.id == "statement" and saved_variant == "personal":
description = ("Lit les blocs SHEETINFO de enonce.tex et récupère les énoncés, solutions "
"et barèmes du service personnel (localhost:8080). Crée un groupe par exercice. "
"Les actions Gemini ci-dessous restent facultatives.")
if missing:
description += "\nPrérequis non détectés : " + ", ".join(missing)
"Les deux étapes Gemini suivantes restent facultatives.")
self.description_label.configure(text=description)
if missing:
self.missing_requirements_label.configure(
text="Prérequis non détectés : " + ", ".join(missing)
)
self.missing_requirements_label.grid()
else:
self.missing_requirements_label.grid_remove()
row = self._render_context_controls(step, 0)
if len(step.variants) > 1:
@@ -765,14 +845,6 @@ class CopienatorApp(tk.Tk):
attach_tooltip(extra_entry, step.extra_arguments_help)
row += 1
if self.show_personal_steps and step.id == "statement":
actions = ttk.LabelFrame(self.form, text="Après génération — facultatif", padding=7)
actions.grid(row=row, column=0, columnspan=2, sticky="ew", pady=(10, 8))
for ident, title in (("statement_groups", "Regrouper avec Gemini…"),
("statement_persp", "Remplacer Persp avec Gemini…")):
ttk.Button(actions, text=title, command=lambda target=ident: self._select_statement_action(target)).pack(
side="left", padx=(0, 6))
self.run_button.configure(text="Enregistrer la sélection" if step.id == "refaire_selection" else ("Marquer terminée" if step.is_manual else "Exécuter"))
self.skip_button.configure(state="normal" if step.optional else "disabled")
self._rendering = False
@@ -790,7 +862,16 @@ class CopienatorApp(tk.Tk):
ttk.Button(self.form, text="Recharger — vérifier à nouveau", command=self._reload_inputs).grid(
row=row, column=0, columnspan=2, sticky="w", pady=(0, 8)
)
return row + 1
row += 1
if self.show_personal_steps:
self.get_input_files_button = ttk.Button(
self.form, text="Get the files", command=self._get_input_files
)
self.get_input_files_button.grid(
row=row, column=0, columnspan=2, sticky="w", pady=(0, 8)
)
row += 1
return row
if step.section == REFAIRE_SECTION:
evaluation = self.evaluation
if step.id in {"refaire_selection", "refaire_correct"}:
@@ -891,14 +972,6 @@ class CopienatorApp(tk.Tk):
row += 1
return row
def _select_statement_action(self, step_id: str) -> None:
self._save_current_form()
target = self.arg_vars.get("target")
if target:
self.state_store.update_step(step_id, values={"target": target.get()})
self.tree.selection_set(step_id)
self.tree.see(step_id)
def _refaire_restart_controls(self, row: int) -> int:
controls = ttk.Frame(self.form)
controls.grid(row=row, column=0, columnspan=2, sticky="w", pady=(0, 5))
+3 -4
View File
@@ -102,10 +102,9 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
"Prétraitement de l’énoncé",
"Analyser l’énoncé",
"Détecte les questions, leurs groupes, le corrigé et les indications de barème.",
(
python("gemini", "Analyse avec Gemini", "statement"),
) + ((python("personal", "Énoncés et solutions personnels (SHEETINFO)", "statement-personal"),)
if show_personal_steps else ()),
((python("personal", "Énoncés et solutions personnels (SHEETINFO)", "statement-personal"),)
if show_personal_steps else ())
+ (python("gemini", "Analyse avec Gemini", "statement"),),
arguments=(
arg_target("le dossier de l’évaluation"),
ArgumentSpec(