This commit is contained in:
2026-09-08 16:41:04 +02:00
parent db4ed2ef31
commit bf05272797
14 changed files with 725 additions and 75 deletions
+96 -26
View File
@@ -8,7 +8,8 @@ from pathlib import Path
from tkinter import filedialog, messagebox, ttk
from typing import Any
from copienator import EvaluationWorkspace, ExitCode, atomic_write_json
from copienator import CliError, EvaluationWorkspace, ExitCode, atomic_write_json
from copienator.copy_errors import copy_errors
from copienator.platform import (
WindowsLabelError,
add_platform_executable_paths,
@@ -205,25 +206,29 @@ class CopienatorApp(tk.Tk):
top = ttk.Frame(self, padding=(10, 8))
top.grid(row=0, column=0, sticky="ew")
top.columnconfigure(2, weight=1)
top.columnconfigure(5, 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=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=3, padx=6
self.evaluation_entry = ttk.Entry(top, textvariable=self.evaluation_var, width=42)
self.evaluation_entry.grid(row=0, column=2, sticky="w")
self.evaluation_entry.bind("<Return>", lambda _event: self._load_evaluation())
self.open_evaluation_button = ttk.Button(
top, text="Ouvrir le dossier", command=self._open_evaluation_folder
)
ttk.Button(top, text="Diagnostic…", command=self._show_diagnostics).grid(row=0, column=4, padx=(6, 0))
self.open_evaluation_button.grid(row=0, column=3, padx=(6, 0))
ttk.Button(top, text="Parcourir…", command=self._browse_evaluation).grid(
row=0, column=4, padx=6
)
ttk.Button(top, text="Diagnostic…", command=self._show_diagnostics).grid(row=0, column=6, padx=(6, 0))
ttk.Label(top, text="Clé Gemini").grid(row=1, column=0, sticky="w", padx=(0, 8), pady=(7, 0))
ttk.Entry(top, textvariable=self.api_key_var, show="", width=32).grid(
row=1, column=1, sticky="w", pady=(7, 0)
)
environment = ttk.Frame(top)
environment.grid(row=1, column=2, columnspan=3, sticky="e", pady=(7, 0))
environment.grid(row=1, column=2, columnspan=5, sticky="e", pady=(7, 0))
ttk.Checkbutton(
environment,
text="Utiliser le proxy HTTPS",
@@ -403,6 +408,9 @@ class CopienatorApp(tk.Tk):
self.evaluation_var.set(selected)
self._load_evaluation()
def _open_evaluation_folder(self) -> None:
self._open_desktop_path(self.evaluation, "dossier d’évaluation")
def _toggle_proxy(self) -> None:
self.proxy_entry.configure(state="normal" if self.use_proxy_var.get() else "disabled")
@@ -581,10 +589,12 @@ class CopienatorApp(tk.Tk):
return
self._rendering = True
redo = step.section == REFAIRE_SECTION
self.console.configure(height=8 if redo else 14)
self.rowconfigure(1, weight=4 if redo else 3)
self.rowconfigure(2, weight=1 if redo else 2)
self.form_canvas.configure(height=(340 if step.id == "refaire_selection" else 260) if redo else 200)
expanded_form = redo or step.id in {"page_splitter", "cutleft", "labels"}
self.console.configure(height=8 if expanded_form else 14)
self.rowconfigure(1, weight=4 if expanded_form else 3)
self.rowconfigure(2, weight=1 if expanded_form else 2)
form_height = (340 if step.id == "refaire_selection" else 260) if redo else (300 if expanded_form else 200)
self.form_canvas.configure(height=form_height)
self.form_canvas.yview_moveto(0)
for child in self.form.winfo_children():
child.destroy()
@@ -667,6 +677,15 @@ class CopienatorApp(tk.Tk):
row=row, column=0, sticky="w", pady=(10, 4), padx=(0, 8)
)
ttk.Entry(self.form, textvariable=self.extra_var).grid(row=row, column=1, sticky="ew", pady=(10, 4))
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")
@@ -675,13 +694,11 @@ 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))
if step.id == "rename":
self.validate_rename_button = ttk.Button(
self.form, text="Valider sans renommer", command=self._validate_rename_step
)
self.validate_rename_button.grid(row=row, column=0, columnspan=2, sticky="w", pady=(0, 8))
row += 1
if step.id == "inputs":
ttk.Button(self.form, text="Recharger — vérifier à nouveau", command=self._reload_inputs).grid(
@@ -714,7 +731,7 @@ class CopienatorApp(tk.Tk):
return row + 1
if step.id in {"review_persp", "correction"}:
row = self._correction_folder_buttons(row)
if step.section == "Prétraitement des copies":
if step.id in {"page_splitter", "cutleft", "labels"}:
evaluation = self.evaluation
paths = copy_pdf_paths(evaluation) if evaluation else []
self.copy_paths = {path.name: path for path in paths}
@@ -745,14 +762,36 @@ 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":
if step.id in {"page_splitter", "cutleft", "labels"}:
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,
label = "Refaire la copie sélectionnée" if step.id == "page_splitter" else "Cibler la copie sélectionnée"
ttk.Button(actions, text=label, command=self._target_selected_copy,
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 loriginal conservé. Vérifiez la commande puis cliquez sur Exécuter.",
help_text = (
"La reprise utilise loriginal conservé. "
if step.id == "page_splitter"
else "Seule la copie ciblée sera analysée par Gemini. "
if step.id == "labels"
else "Seule la copie ciblée sera traitée. "
)
ttk.Label(copies, text=help_text + "Vérifiez la commande puis cliquez sur Exécuter.",
wraplength=570).grid(row=3, column=0, columnspan=2, sticky="w", pady=(5, 0))
if step.id in {"page_splitter", "cutleft"}:
try:
marked = copy_errors(EvaluationWorkspace(evaluation)) if evaluation else {}
marked_summary = ", ".join(marked) or "Aucune copie signalée."
except (CliError, OSError, ValueError, RuntimeError) as exc:
marked = {}
marked_summary = f"Lecture des copies signalées impossible : {exc}"
self.marked_copies_button = ttk.Button(
copies, text=f"Traiter les copies signalées ({len(marked)})", command=self._run_marked_copies,
state="normal" if marked and not self.active_step_id and not self.runner.running else "disabled",
)
self.marked_copies_button.grid(row=4, column=0, columnspan=2, sticky="w", pady=(7, 0))
ttk.Label(copies, text=marked_summary, wraplength=570).grid(
row=5, column=0, columnspan=2, sticky="w")
row += 1
if step.id == "plotting":
@@ -852,16 +891,28 @@ 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:
def _target_selected_copy(self) -> None:
path = self.copy_paths.get(self.copy_var.get())
if path and "target" in self.arg_vars:
if "marked" in self.arg_vars:
self.arg_vars["marked"].set(False)
self.arg_vars["target"].set(str(path))
self.info_var.set(f"Reprise de {path.name} prête — cliquez sur Exécuter.")
self.info_var.set(f"Copie ciblée : {path.name} — cliquez sur Exécuter.")
def _target_all_pages(self) -> None:
if "target" in self.arg_vars:
if "marked" in self.arg_vars:
self.arg_vars["marked"].set(False)
self.arg_vars["target"].set(self._evaluation_arg())
def _run_marked_copies(self) -> None:
if (not self.current_step or self.current_step.id not in {"page_splitter", "cutleft"}
or self.active_step_id or self.runner.running or not self.state_store.evaluation):
return
self.arg_vars["target"].set(self._evaluation_arg())
self.arg_vars["marked"].set(True)
self._run_current_step()
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.")
@@ -1186,6 +1237,19 @@ class CopienatorApp(tk.Tk):
f"{self.current_step.title} : {STATUS_LABELS.get(status, status).lower()}{detail}."
)
def _validate_rename_step(self) -> None:
if (not self.current_step or self.current_step.id != "rename"
or not self.state_store.evaluation or self.active_step_id or self.runner.running):
return
self._save_current_form()
self.state_store.update_step("rename", status="success")
self.state_store.add_history({
"step": "rename", "status": "success", "manual": True,
"reason": "Noms des PDF validés sans renommer",
})
self._populate_tree()
self.info_var.set("Renommage validé — aucun fichier modifié.")
def _skip_step(self) -> None:
if self.current_step and self.current_step.optional:
step_id = self.current_step.id
@@ -1314,6 +1378,12 @@ class CopienatorApp(tk.Tk):
def _update_controls(self) -> None:
running = bool(self.active_step_id) or self.runner.running
if running and self.current_step and self.current_step.id in {"page_splitter", "cutleft"}:
self.marked_copies_button.configure(state="disabled")
if self.current_step and self.current_step.id == "rename":
self.validate_rename_button.configure(
state="disabled" if running or not self.state_store.evaluation else "normal"
)
self.run_button.configure(state="disabled" if running or not self.current_step else "normal")
self.skip_button.configure(state="normal" if not running and self.current_step and self.current_step.optional else "disabled")
self.interrupt_button.configure(state="normal" if running else "disabled")
+2 -1
View File
@@ -173,7 +173,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
"Séparer et réordonner les pages",
"Ouvre loutil interactif de découpage A3 vers A4. La cible peut être un dossier ou un PDF.",
(python("default", "Séparation des pages", "page-split"),),
arguments=(arg_target(),),
arguments=(arg_target(), ArgumentSpec("marked", "Copies signalées uniquement", "bool", "--marked")),
artifacts=("Copies", "Copies Originales"),
),
StepDefinition(
@@ -185,6 +185,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
arguments=(
arg_target(),
ArgumentSpec("fullpage", "Toujours utiliser la page entière", "bool", "--fullpage"),
ArgumentSpec("marked", "Copies signalées uniquement", "bool", "--marked"),
),
requires=("Copies",),
artifacts=("Cutleft",),