start-gui.sh et améliorations diverses
This commit is contained in:
+120
-2
@@ -279,6 +279,8 @@ class CopienatorApp(tk.Tk):
|
||||
ttk.Label(command_box, textvariable=self.command_var, wraplength=690, justify="left").grid(
|
||||
row=0, column=0, sticky="ew"
|
||||
)
|
||||
self.copy_command_button = ttk.Button(command_box, text="Copier la commande", command=self._copy_command)
|
||||
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))
|
||||
@@ -304,7 +306,22 @@ class CopienatorApp(tk.Tk):
|
||||
foreground="#efefef",
|
||||
insertbackground="white",
|
||||
state="disabled",
|
||||
takefocus=True,
|
||||
exportselection=False,
|
||||
)
|
||||
self.console.bind("<Control-c>", self._copy_console_selection)
|
||||
self.console.bind("<Control-Shift-C>", self._copy_console_selection)
|
||||
self.console.bind("<Control-a>", self._select_console_all)
|
||||
self.console.bind("<Button-1>", lambda _event: self.console.focus_set())
|
||||
self.console_menu = tk.Menu(self.console, tearoff=False)
|
||||
self.console_menu.add_command(label="Copier la sélection", command=self._copy_console_selection)
|
||||
self.console_menu.add_command(label="Tout sélectionner", command=self._select_console_all)
|
||||
self.console_menu.add_command(label="Copier toute la sortie", command=self._copy_console_all)
|
||||
self.console.bind("<Button-3>", self._show_console_menu)
|
||||
if self.tk.call("tk", "windowingsystem") == "aqua":
|
||||
self.console.bind("<Command-c>", self._copy_console_selection)
|
||||
self.console.bind("<Command-a>", self._select_console_all)
|
||||
self.console.bind("<Button-2>", self._show_console_menu)
|
||||
console_scroll = ttk.Scrollbar(console_frame, orient="vertical", command=self.console.yview)
|
||||
self.console.configure(yscrollcommand=console_scroll.set)
|
||||
self.console.grid(row=0, column=0, sticky="nsew")
|
||||
@@ -313,6 +330,10 @@ class CopienatorApp(tk.Tk):
|
||||
console_actions = ttk.Frame(console_frame)
|
||||
console_actions.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(5, 0))
|
||||
console_actions.columnconfigure(1, weight=1)
|
||||
clipboard_actions = ttk.Frame(console_actions)
|
||||
clipboard_actions.grid(row=1, column=0, columnspan=6, sticky="w", pady=(5, 0))
|
||||
ttk.Button(clipboard_actions, text="Copier la sélection", command=self._copy_console_selection).pack(side="left")
|
||||
ttk.Button(clipboard_actions, text="Copier toute la sortie", command=self._copy_console_all).pack(side="left", padx=6)
|
||||
ttk.Label(console_actions, text="Réponse au script").grid(row=0, column=0, padx=(0, 6))
|
||||
self.stdin_var = tk.StringVar()
|
||||
self.stdin_entry = ttk.Entry(console_actions, textvariable=self.stdin_var)
|
||||
@@ -467,7 +488,13 @@ class CopienatorApp(tk.Tk):
|
||||
if not evaluation:
|
||||
return list(step.requires)
|
||||
missing = []
|
||||
for pattern in step.requires:
|
||||
requirements = step.requires
|
||||
if step.id == "statement":
|
||||
variant = (self.variant_var.get() if self.current_step and self.current_step.id == step.id
|
||||
else self.state_store.step(step.id).get("variant", "gemini"))
|
||||
if variant == "personal":
|
||||
requirements = ("enonce.tex",)
|
||||
for pattern in requirements:
|
||||
if any(char in pattern for char in "*?["):
|
||||
exists = next(evaluation.glob(pattern), None) is not None
|
||||
else:
|
||||
@@ -475,8 +502,19 @@ class CopienatorApp(tk.Tk):
|
||||
exists = path.exists()
|
||||
if not exists:
|
||||
missing.append(pattern)
|
||||
if step.id == "inputs" and not ((evaluation / "names").is_file() or (self.repository / "names").is_file()):
|
||||
missing.append("names")
|
||||
return missing
|
||||
|
||||
def _reload_inputs(self) -> None:
|
||||
if not self.state_store.evaluation or self.active_step_id or self.runner.running:
|
||||
return
|
||||
missing = self._missing_requirements(self.step_by_id["inputs"])
|
||||
self.state_store.update_step("inputs", status="ready" if missing else "success")
|
||||
self._populate_tree()
|
||||
self._render_step()
|
||||
self.info_var.set("Fichiers revérifiés — " + ("manquants : " + ", ".join(missing) if missing else "tous les fichiers d’entrée sont présents."))
|
||||
|
||||
def _on_tree_select(self, _event: tk.Event[Any] | None = None) -> None:
|
||||
selection = self.tree.selection()
|
||||
if not selection or selection[0].startswith("section:"):
|
||||
@@ -561,6 +599,10 @@ class CopienatorApp(tk.Tk):
|
||||
self.title_label.configure(text=step.title)
|
||||
missing = self._missing_requirements(step)
|
||||
description = step.description
|
||||
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)
|
||||
self.description_label.configure(text=description)
|
||||
@@ -633,6 +675,19 @@ class CopienatorApp(tk.Tk):
|
||||
self._update_controls()
|
||||
|
||||
def _render_context_controls(self, step: StepDefinition, row: int) -> int:
|
||||
if self.show_personal_steps and step.id in {"statement", "statement_groups", "statement_persp", "review_persp"}:
|
||||
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=(0, 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))
|
||||
row += 1
|
||||
if step.id == "inputs":
|
||||
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
|
||||
if step.section == REFAIRE_SECTION:
|
||||
evaluation = self.evaluation
|
||||
if step.id in {"refaire_selection", "refaire_correct"}:
|
||||
@@ -675,7 +730,8 @@ class CopienatorApp(tk.Tk):
|
||||
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 "")
|
||||
if self.copy_var.get() not in names:
|
||||
self.copy_var.set(names[0] if names else "")
|
||||
selector = ttk.Combobox(
|
||||
copies,
|
||||
textvariable=self.copy_var,
|
||||
@@ -689,6 +745,14 @@ class CopienatorApp(tk.Tk):
|
||||
command=self._open_selected_copy,
|
||||
state="normal" if names else "disabled",
|
||||
).grid(row=1, column=1, padx=(6, 0), pady=(7, 0))
|
||||
if step.id == "page_splitter":
|
||||
actions = ttk.Frame(copies)
|
||||
actions.grid(row=2, column=0, columnspan=2, sticky="w", pady=(7, 0))
|
||||
ttk.Button(actions, text="Refaire la copie sélectionnée", command=self._redo_selected_pages,
|
||||
state="normal" if names else "disabled").pack(side="left")
|
||||
ttk.Button(actions, text="Cibler tout le dossier", command=self._target_all_pages).pack(side="left", padx=6)
|
||||
ttk.Label(copies, text="La reprise utilise l’original conservé. Vérifiez la commande puis cliquez sur Exécuter.",
|
||||
wraplength=570).grid(row=3, column=0, columnspan=2, sticky="w", pady=(5, 0))
|
||||
row += 1
|
||||
|
||||
if step.id == "plotting":
|
||||
@@ -702,6 +766,14 @@ 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))
|
||||
@@ -780,6 +852,16 @@ class CopienatorApp(tk.Tk):
|
||||
def _open_selected_copy(self) -> None:
|
||||
self._open_desktop_path(self.copy_paths.get(self.copy_var.get()), "copie")
|
||||
|
||||
def _redo_selected_pages(self) -> None:
|
||||
path = self.copy_paths.get(self.copy_var.get())
|
||||
if path and "target" in self.arg_vars:
|
||||
self.arg_vars["target"].set(str(path))
|
||||
self.info_var.set(f"Reprise de {path.name} prête — cliquez sur Exécuter.")
|
||||
|
||||
def _target_all_pages(self) -> None:
|
||||
if "target" in self.arg_vars:
|
||||
self.arg_vars["target"].set(self._evaluation_arg())
|
||||
|
||||
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} n’existe pas encore.")
|
||||
@@ -907,6 +989,7 @@ class CopienatorApp(tk.Tk):
|
||||
def _update_command_preview(self) -> None:
|
||||
if self._rendering or not self.current_step:
|
||||
return
|
||||
self.copy_command_button.configure(state="disabled")
|
||||
if self.current_step.is_manual:
|
||||
self.command_var.set("La sélection sera utilisée pour toutes les étapes de ce parcours." if self.current_step.id == "refaire_selection" else "Étape manuelle — aucune commande ne sera exécutée.")
|
||||
return
|
||||
@@ -916,9 +999,44 @@ class CopienatorApp(tk.Tk):
|
||||
if len(commands) > 2:
|
||||
preview += f"\nPuis {len(commands) - 2} autres copies, successivement."
|
||||
self.command_var.set(preview)
|
||||
self.copy_command_button.configure(state="normal")
|
||||
except (OSError, ValueError, TypeError) as exc:
|
||||
self.command_var.set(f"Arguments invalides : {exc}")
|
||||
|
||||
def _copy_text(self, text: str) -> None:
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(text)
|
||||
|
||||
def _copy_command(self) -> None:
|
||||
if not self.current_step or self.current_step.is_manual:
|
||||
return
|
||||
try:
|
||||
commands = self._refaire_commands() if self.current_step.section == REFAIRE_SECTION else [self._make_command()]
|
||||
self._copy_text("\n".join(command_display(command) for command in commands))
|
||||
self.info_var.set("Commande(s) copiée(s) — à exécuter depuis " + str(self.repository))
|
||||
except (OSError, ValueError, TypeError) as exc:
|
||||
self.info_var.set(f"Copie impossible : {exc}")
|
||||
|
||||
def _copy_console_selection(self, _event=None) -> str:
|
||||
if self.console.tag_ranges("sel"):
|
||||
self._copy_text(self.console.get("sel.first", "sel.last"))
|
||||
return "break"
|
||||
|
||||
def _select_console_all(self, _event=None) -> str:
|
||||
self.console.tag_add("sel", "1.0", "end-1c")
|
||||
self.console.focus_set()
|
||||
return "break"
|
||||
|
||||
def _copy_console_all(self) -> None:
|
||||
self._copy_text(self.console.get("1.0", "end-1c"))
|
||||
|
||||
def _show_console_menu(self, event) -> str:
|
||||
try:
|
||||
self.console_menu.tk_popup(event.x_root, event.y_root)
|
||||
finally:
|
||||
self.console_menu.grab_release()
|
||||
return "break"
|
||||
|
||||
def _browse_target_file(self, variable: tk.Variable) -> None:
|
||||
selected = filedialog.askopenfilename(initialdir=self.evaluation or self.repository)
|
||||
if selected:
|
||||
|
||||
@@ -51,7 +51,7 @@ def collect_diagnostics(
|
||||
"pdf2image": "pdf2image",
|
||||
"reportlab": "reportlab",
|
||||
"img2pdf": "img2pdf",
|
||||
"PyMuPDF": "fitz",
|
||||
"PyMuPDF": "pymupdf",
|
||||
"ftfy": "ftfy",
|
||||
"ezodf": "ezodf",
|
||||
"Google GenAI": "google.genai",
|
||||
|
||||
@@ -92,8 +92,8 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
||||
"Détecte les questions, leurs groupes, le corrigé et les indications de barème.",
|
||||
(
|
||||
python("gemini", "Analyse avec Gemini", "statement"),
|
||||
python("personal", "Alternative personnelle", "statement-personal"),
|
||||
),
|
||||
) + ((python("personal", "Énoncés et solutions personnels (SHEETINFO)", "statement-personal"),)
|
||||
if show_personal_steps else ()),
|
||||
arguments=(
|
||||
arg_target("Dossier de l’évaluation"),
|
||||
ArgumentSpec(
|
||||
@@ -107,6 +107,22 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
||||
requires=("enonce.pdf", "enonce.tex", "correction.tex"),
|
||||
artifacts=("labels", "Text", "Sol", "Persp"),
|
||||
),
|
||||
StepDefinition(
|
||||
"statement_groups", "Prétraitement de l’énoncé", "Regrouper les questions avec Gemini",
|
||||
"Facultatif après la génération : remplace les groupes par exercice par des groupes "
|
||||
"proposés par Gemini, en conservant les labels, les énoncés, les solutions et les barèmes.",
|
||||
(CommandVariant("default", "Groupes Gemini", "statement", fixed_args=("--groups-only",)),),
|
||||
arguments=(arg_target("Dossier de l’évaluation"),),
|
||||
optional=True, personal=True, requires=("labels", "Text2", "Sol2"),
|
||||
),
|
||||
StepDefinition(
|
||||
"statement_persp", "Prétraitement de l’énoncé", "Remplacer les barèmes par Gemini",
|
||||
"Facultatif : remplace Persp par des barèmes Gemini sur 4 points, pour les groupes actuels. "
|
||||
"Les énoncés et les solutions personnels sont conservés.",
|
||||
(CommandVariant("default", "Barèmes Gemini", "statement", fixed_args=("--persp-only",)),),
|
||||
arguments=(arg_target("Dossier de l’évaluation"),),
|
||||
optional=True, personal=True, requires=("labels", "label_groups", "Text2", "Sol2"),
|
||||
),
|
||||
StepDefinition(
|
||||
"review_persp",
|
||||
"Prétraitement de l’énoncé",
|
||||
|
||||
Reference in New Issue
Block a user