améliorations diverses
This commit is contained in:
+170
-12
@@ -22,6 +22,7 @@ from copienator.platform import (
|
||||
|
||||
from .diagnostics import collect_diagnostics
|
||||
from .batch_monitor import BatchMonitor
|
||||
from .giving_names import GivingNamesPanel, find_name_issues
|
||||
from .notifications import notify_desktop
|
||||
from .manual_resolution import ManualResolutionPanel
|
||||
from .refaire import SECTION as REFAIRE_SECTION
|
||||
@@ -58,6 +59,9 @@ STATUS_LABELS = {
|
||||
}
|
||||
|
||||
DEFAULT_HTTPS_PROXY = "http://10.0.0.1:3128"
|
||||
CORRECTION_PROGRESS_RE = re.compile(
|
||||
r"\[Progression correction\] Groupes traités : (\d+)/(\d+)"
|
||||
)
|
||||
PERSONAL_INTERRO_SOURCE = Path("/home/sebastien/Prépa/Staging/Interro")
|
||||
ANNOTATION_DIRECTORIES = ("BGnot", "Bnot", "Anot")
|
||||
ANNOTATION_VARIANT_DIRECTORIES = {
|
||||
@@ -65,6 +69,10 @@ ANNOTATION_VARIANT_DIRECTORIES = {
|
||||
"checks": "Bnot",
|
||||
"grouped": "BGnot",
|
||||
}
|
||||
ANNOTATION_VARIANT_READERS = {
|
||||
"checks": "standard",
|
||||
"grouped": "grouped",
|
||||
}
|
||||
|
||||
|
||||
def get_personal_interro_files(
|
||||
@@ -259,8 +267,11 @@ class CopienatorApp(tk.Tk):
|
||||
self.copy_paths: dict[str, Path] = {}
|
||||
self._rendering = False
|
||||
self.refaire_panel: RefaireSelection | None = None
|
||||
self.giving_names_panel: GivingNamesPanel | None = None
|
||||
self.complete_giving_names_button: ttk.Button | None = None
|
||||
self.pending_refaire_commands: list[list[str]] = []
|
||||
self.refaire_command_index = 0
|
||||
self._correction_progress_buffer = ""
|
||||
|
||||
self.title("Copienator — assistant de correction")
|
||||
self.geometry("1180x820")
|
||||
@@ -752,7 +763,15 @@ class CopienatorApp(tk.Tk):
|
||||
return
|
||||
self._rendering = True
|
||||
redo = step.section == REFAIRE_SECTION
|
||||
expanded_form = redo or step.id in {"page_splitter", "crop_blank_margins", "cutleft", "labels", "manual_resolution"}
|
||||
expanded_form = redo or step.id in {
|
||||
"page_splitter",
|
||||
"crop_blank_margins",
|
||||
"cutleft",
|
||||
"labels",
|
||||
"plotting",
|
||||
"manual_resolution",
|
||||
"giving_names",
|
||||
}
|
||||
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)
|
||||
@@ -763,6 +782,8 @@ class CopienatorApp(tk.Tk):
|
||||
child.destroy()
|
||||
self.arg_vars.clear()
|
||||
self.refaire_panel = None
|
||||
self.giving_names_panel = None
|
||||
self.complete_giving_names_button = None
|
||||
entry = self.state_store.step(step.id) if self.state_store.evaluation else {}
|
||||
saved_variant = entry.get("variant", step.variants[0].id)
|
||||
if saved_variant not in {variant.id for variant in step.variants}:
|
||||
@@ -909,6 +930,31 @@ class CopienatorApp(tk.Tk):
|
||||
)
|
||||
row += 1
|
||||
return row
|
||||
if step.id == "giving_names" and self.evaluation:
|
||||
self.giving_names_panel = GivingNamesPanel(
|
||||
self.form, self.evaluation
|
||||
)
|
||||
self.giving_names_panel.grid(
|
||||
row=row,
|
||||
column=0,
|
||||
columnspan=2,
|
||||
sticky="ew",
|
||||
pady=(0, 8),
|
||||
)
|
||||
row += 1
|
||||
self.complete_giving_names_button = ttk.Button(
|
||||
self.form,
|
||||
text="Marquer terminée",
|
||||
command=self._complete_giving_names,
|
||||
)
|
||||
self.complete_giving_names_button.grid(
|
||||
row=row,
|
||||
column=0,
|
||||
columnspan=2,
|
||||
sticky="w",
|
||||
pady=(0, 8),
|
||||
)
|
||||
row += 1
|
||||
if step.section == REFAIRE_SECTION:
|
||||
evaluation = self.evaluation
|
||||
if step.id in {"refaire_selection", "refaire_correct"}:
|
||||
@@ -935,7 +981,14 @@ class CopienatorApp(tk.Tk):
|
||||
return row + 1
|
||||
if step.id in {"review_persp", "correction"}:
|
||||
row = self._correction_folder_buttons(row)
|
||||
if step.id in {"page_splitter", "crop_blank_margins", "cutleft", "labels"}:
|
||||
copy_selection_steps = {
|
||||
"page_splitter",
|
||||
"crop_blank_margins",
|
||||
"cutleft",
|
||||
"labels",
|
||||
"plotting",
|
||||
}
|
||||
if step.id in copy_selection_steps:
|
||||
evaluation = self.evaluation
|
||||
paths = copy_pdf_paths(evaluation) if evaluation else []
|
||||
self.copy_paths = {path.name: path for path in paths}
|
||||
@@ -966,7 +1019,7 @@ 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 in {"page_splitter", "crop_blank_margins", "cutleft", "labels"}:
|
||||
if step.id in copy_selection_steps:
|
||||
actions = ttk.Frame(copies)
|
||||
actions.grid(row=2, column=0, columnspan=2, sticky="w", pady=(7, 0))
|
||||
label = "Refaire la copie sélectionnée" if step.id == "page_splitter" else "Cibler la copie sélectionnée"
|
||||
@@ -978,6 +1031,8 @@ class CopienatorApp(tk.Tk):
|
||||
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 vérifiée. "
|
||||
if step.id == "plotting"
|
||||
else "Seule la copie ciblée sera traitée. "
|
||||
)
|
||||
ttk.Label(copies, text=help_text + "Vérifiez la commande puis cliquez sur Exécuter.",
|
||||
@@ -1352,8 +1407,7 @@ class CopienatorApp(tk.Tk):
|
||||
return
|
||||
if step.is_manual:
|
||||
self._mark_step("success")
|
||||
if step.section == REFAIRE_SECTION:
|
||||
self._move_selection_from(step.id, 1)
|
||||
self._move_selection_from(step.id, 1)
|
||||
return
|
||||
if os.name == "nt" and step.id != "statement":
|
||||
labels_path = evaluation / "labels"
|
||||
@@ -1400,6 +1454,9 @@ class CopienatorApp(tk.Tk):
|
||||
last_run_values=run_values,
|
||||
)
|
||||
self.active_step_id = step.id
|
||||
if step.id == "correction":
|
||||
self._correction_progress_buffer = ""
|
||||
self.info_var.set("Correction : préparation des groupes…")
|
||||
workspace = self.state_store.workspace
|
||||
assert workspace is not None
|
||||
log_path = workspace.log_path(step.id)
|
||||
@@ -1463,6 +1520,33 @@ class CopienatorApp(tk.Tk):
|
||||
self._populate_tree()
|
||||
self.info_var.set("Renommage validé — aucun fichier modifié.")
|
||||
|
||||
def _complete_giving_names(self) -> None:
|
||||
if (
|
||||
not self.current_step
|
||||
or self.current_step.id != "giving_names"
|
||||
or not self.evaluation
|
||||
or self.active_step_id
|
||||
or self.runner.running
|
||||
):
|
||||
return
|
||||
return_dir = EvaluationWorkspace(self.evaluation).return_dir
|
||||
if not return_dir.is_dir():
|
||||
messagebox.showerror(
|
||||
"A Rendre absent",
|
||||
"Exécutez d’abord l’attribution des noms.",
|
||||
)
|
||||
return
|
||||
issues = find_name_issues(self.evaluation)
|
||||
if issues and not messagebox.askyesno(
|
||||
"Noms encore à vérifier",
|
||||
f"{len(issues)} copie(s) ont encore un nom inconnu ou dupliqué. "
|
||||
"Marquer quand même l’étape comme terminée ?",
|
||||
icon="warning",
|
||||
):
|
||||
return
|
||||
self._mark_step("success")
|
||||
self._move_selection_from("giving_names", 1)
|
||||
|
||||
def _skip_step(self) -> None:
|
||||
if self.current_step and self.current_step.optional:
|
||||
step_id = self.current_step.id
|
||||
@@ -1477,7 +1561,9 @@ class CopienatorApp(tk.Tk):
|
||||
except queue.Empty:
|
||||
break
|
||||
if event == "output":
|
||||
self._append_console(str(payload))
|
||||
output = str(payload)
|
||||
self._track_correction_progress(output)
|
||||
self._append_console(output)
|
||||
elif event == "input_echo":
|
||||
self._append_console(f"> {payload}")
|
||||
elif event == "runner_error":
|
||||
@@ -1487,6 +1573,24 @@ class CopienatorApp(tk.Tk):
|
||||
self._finish_process(int(return_code), bool(interrupted))
|
||||
self.after(60, self._poll_runner)
|
||||
|
||||
def _track_correction_progress(self, output: str) -> None:
|
||||
if self.active_step_id != "correction":
|
||||
return
|
||||
self._correction_progress_buffer = (
|
||||
self._correction_progress_buffer + output
|
||||
)[-512:]
|
||||
matches = list(CORRECTION_PROGRESS_RE.finditer(
|
||||
self._correction_progress_buffer
|
||||
))
|
||||
if not matches:
|
||||
return
|
||||
completed, total = (int(value) for value in matches[-1].groups())
|
||||
percent = round(100 * completed / total) if total else 100
|
||||
self.info_var.set(
|
||||
f"Correction : {completed} groupe(s) traité(s) sur {total} "
|
||||
f"({percent} %)."
|
||||
)
|
||||
|
||||
def _batch_watch_status_changed(self, message: str) -> None:
|
||||
self.batch_watch_status.set(message)
|
||||
self._update_controls()
|
||||
@@ -1565,7 +1669,25 @@ class CopienatorApp(tk.Tk):
|
||||
"été conservés.",
|
||||
)
|
||||
return
|
||||
self.state_store.update_step(step_id, status=status, return_code=return_code)
|
||||
stored_status = (
|
||||
"detected"
|
||||
if step_id == "giving_names" and status == "success"
|
||||
else status
|
||||
)
|
||||
self.state_store.update_step(
|
||||
step_id, status=stored_status, return_code=return_code
|
||||
)
|
||||
if step_id == "annotation" and status == "success":
|
||||
generation_variant = self.state_store.step("annotation").get(
|
||||
"last_run_variant"
|
||||
)
|
||||
reader_variant = ANNOTATION_VARIANT_READERS.get(
|
||||
str(generation_variant)
|
||||
)
|
||||
if reader_variant:
|
||||
self.state_store.update_step(
|
||||
"read_annotations", variant=reader_variant
|
||||
)
|
||||
self.state_store.add_history(
|
||||
{
|
||||
"step": step_id,
|
||||
@@ -1580,12 +1702,27 @@ class CopienatorApp(tk.Tk):
|
||||
self.active_step_id = None
|
||||
self._populate_tree()
|
||||
self._update_controls()
|
||||
if step_id == "giving_names" and status == "success":
|
||||
if self.giving_names_panel:
|
||||
self.giving_names_panel.reload()
|
||||
self.info_var.set(
|
||||
"A Rendre a été préparé. Vérifiez les noms, puis cliquez "
|
||||
"sur « Marquer terminée »."
|
||||
)
|
||||
return
|
||||
if status == "success":
|
||||
if step_id == "batch_status" and self.batch_monitor.active:
|
||||
self.batch_monitor.stop()
|
||||
self._batch_results_ready()
|
||||
return
|
||||
self.after_idle(lambda completed_id=step_id: self._move_selection_from(completed_id, 1))
|
||||
if step_id == "manual_resolution":
|
||||
self.after_idle(self._select_refaire_correction)
|
||||
else:
|
||||
self.after_idle(
|
||||
lambda completed_id=step_id: self._move_selection_from(
|
||||
completed_id, 1
|
||||
)
|
||||
)
|
||||
|
||||
def _send_input(self) -> None:
|
||||
text = self.stdin_var.get()
|
||||
@@ -1597,12 +1734,21 @@ class CopienatorApp(tk.Tk):
|
||||
self.stdin_var.set("")
|
||||
|
||||
def _interrupt(self) -> None:
|
||||
if self.runner.running and messagebox.askyesno(
|
||||
"Interrompre", "Envoyer une interruption au script en cours ?"
|
||||
):
|
||||
graceful_correction = self.active_step_id == "correction"
|
||||
question = (
|
||||
"Arrêter de lancer de nouveaux appels Gemini ? Les appels déjà en "
|
||||
"cours pourront se terminer et leurs résultats seront sauvegardés."
|
||||
if graceful_correction
|
||||
else "Envoyer une interruption au script en cours ?"
|
||||
)
|
||||
if self.runner.running and messagebox.askyesno("Interrompre", question):
|
||||
try:
|
||||
self.runner.interrupt()
|
||||
self.info_var.set("Interruption demandée. Utilisez « Forcer l’arrêt » si le script ne répond pas.")
|
||||
self.info_var.set(
|
||||
"Arrêt demandé : attente des appels Gemini en cours…"
|
||||
if graceful_correction
|
||||
else "Interruption demandée. Utilisez « Forcer l’arrêt » si le script ne répond pas."
|
||||
)
|
||||
except OSError as exc:
|
||||
messagebox.showerror("Interruption impossible", str(exc))
|
||||
|
||||
@@ -1645,6 +1791,10 @@ class CopienatorApp(tk.Tk):
|
||||
self.force_button.configure(state="normal" if running else "disabled")
|
||||
self.send_button.configure(state="normal" if running else "disabled")
|
||||
self.stdin_entry.configure(state="normal" if running else "disabled")
|
||||
if self.complete_giving_names_button is not None:
|
||||
self.complete_giving_names_button.configure(
|
||||
state="disabled" if running else "normal"
|
||||
)
|
||||
|
||||
def _move_selection(self, delta: int) -> None:
|
||||
if not self.current_step:
|
||||
@@ -1663,6 +1813,14 @@ class CopienatorApp(tk.Tk):
|
||||
self.tree.selection_set(ids[target])
|
||||
self.tree.see(ids[target])
|
||||
|
||||
def _select_refaire_correction(self) -> None:
|
||||
"""Return from manual conflict resolution to the main redo correction."""
|
||||
if self.runner.running or self.active_step_id or not self.state_store.evaluation:
|
||||
return
|
||||
self.state_store.update_step("correction", variant="refaire")
|
||||
self.tree.selection_set("correction")
|
||||
self.tree.see("correction")
|
||||
|
||||
def _on_close(self) -> None:
|
||||
if self.runner.running:
|
||||
if not messagebox.askyesno(
|
||||
|
||||
Reference in New Issue
Block a user