Initial commit for finalisation web interface

This commit is contained in:
sebastien committed 2026-11-01 08:41:37 +01:00
1 parent b295f871a7
commit c36642e8de
33 files changed
+2745 -138

No files matched your search

+108
View File
@@ -279,6 +279,7 @@ class CopienatorApp(tk.Tk):
self.step_by_id = {step.id: step for step in self.steps}
self.state_store = StateStore()
self.runner = ProcessRunner()
self.web_session = None
self.active_step_id: str | None = None
self.current_step: StepDefinition | None = None
self.arg_vars: dict[str, tk.Variable] = {}
@@ -359,6 +360,7 @@ class CopienatorApp(tk.Tk):
top, text="Ouvrir l’énoncé", command=self._open_statement_pdf
)
self.open_statement_button.grid(row=0, column=5, padx=(6, 0))
ttk.Button(top, text="Finalisation web…", command=self._open_finalisation_web).grid(row=0, column=6, padx=(6, 0))
ttk.Button(top, text="Diagnostic…", command=self._show_diagnostics).grid(row=0, column=7, padx=(6, 0))
ttk.Label(top, text="Clé Gemini").grid(row=1, column=0, sticky="w", padx=(0, 8), pady=(7, 0))
@@ -536,6 +538,107 @@ class CopienatorApp(tk.Tk):
status = ttk.Label(self, textvariable=self.info_var, anchor="w", relief="sunken", padding=(6, 3))
status.grid(row=3, column=0, sticky="ew")
def _open_finalisation_web(self) -> None:
if self.web_session is not None or self.runner.running or self.active_step_id or self.batch_monitor.active:
messagebox.showinfo("Traitement en cours", "Terminez le traitement ou la surveillance en cours avant d’ouvrir la finalisation web.")
return
if not self.evaluation:
messagebox.showerror("Évaluation absente", "Chargez d’abord une évaluation.")
return
self._save_current_form()
dialog = tk.Toplevel(self)
dialog.title("Finalisation web")
dialog.transient(self)
dialog.resizable(False, False)
frame = ttk.Frame(dialog, padding=24)
frame.pack(fill="both", expand=True)
text = tk.StringVar(value="Choisissez le mode utilisé pour lire les annotations manuscrites.")
ttk.Label(frame, textvariable=text, wraplength=500).pack(pady=(0, 12))
mode = tk.StringVar(value=annotation_mode_for_reader(self.state_store.step("read_annotations")))
choice = ttk.Combobox(frame, textvariable=mode, values=("BGnot", "Bnot"), state="readonly")
choice.pack(fill="x", pady=8)
url = tk.StringVar()
link = ttk.Entry(frame, textvariable=url, state="readonly", width=65)
link.pack(fill="x", pady=8)
def open_browser():
import webbrowser
if self.web_session:
webbrowser.open(self.web_session.url)
def restore():
session = self.web_session
self.web_session = None
dialog.grab_release()
dialog.destroy()
if session:
data = session.service.state()
reader = self.state_store.step("read_annotations")
values = dict(reader.get("values", {}))
values["update_score"] = True
if data["dirty"] or session.service.job["status"] != "idle":
self.state_store.invalidate_after(self._progression_ids("read_annotations"), "read_annotations")
self.state_store.update_step("read_annotations", variant="standard" if session.service.mode == "Bnot" else "grouped")
if data["dirty"] or session.service.job["status"] == "failed":
self.state_store.update_step("read_annotations", status="stale", values=values)
self.state_store.update_step("giving_names", status="stale")
elif session.service.job["status"] == "success":
self.state_store.update_step("read_annotations", status="success", values=values)
self.state_store.update_step("giving_names", status="detected")
self._populate_tree()
self._render_step()
self._update_controls()
def poll():
session = self.web_session
if session is None:
return
if not session.alive:
restore()
return
running = session.service.job["running"]
close_button.configure(state="disabled" if running else "normal")
text.set("Régénération en cours. Le GUI reste verrouillé jusqu’à la fin." if running else
"Finalisation ouverte dans le navigateur. Le GUI est verrouillé pendant cette session.")
dialog.after(300, poll)
def stop():
if self.web_session:
try:
self.web_session.request_stop()
text.set("Fermeture du serveur…")
except ValueError as exc:
messagebox.showinfo("Traitement en cours", str(exc), parent=dialog)
else:
restore()
def start():
try:
from copienator.finalisation_server import WebSession
self.web_session = WebSession(self.evaluation, mode.get())
except Exception as exc:
messagebox.showerror("Finalisation web", str(exc), parent=dialog)
return
choice.configure(state="disabled")
start_button.configure(state="disabled")
url.set(self.web_session.url)
browser_button.configure(state="normal")
close_button.configure(text="Arrêter le serveur et reprendre")
open_browser()
poll()
actions = ttk.Frame(frame)
actions.pack(pady=12)
start_button = ttk.Button(actions, text="Ouvrir", command=start)
start_button.pack(side="left", padx=4)
browser_button = ttk.Button(actions, text="Rouvrir le navigateur", command=open_browser, state="disabled")
browser_button.pack(side="left", padx=4)
close_button = ttk.Button(actions, text="Annuler", command=stop)
close_button.pack(side="left", padx=4)
dialog.protocol("WM_DELETE_WINDOW", stop)
dialog.grab_set()
dialog.focus_set()
def _show_diagnostics(self) -> None:
checks = collect_diagnostics(
self.show_personal_steps, self.api_key_var.get(), self.evaluation
@@ -1617,6 +1720,8 @@ class CopienatorApp(tk.Tk):
return True
def _run_current_step(self, *, syncthing_ready=False, review_command=None, orientation_repair=False) -> None:
if self.web_session is not None:
return
step = self.current_step
evaluation = self.evaluation
if orientation_repair and (not step or step.id != 'labels'):
@@ -2328,6 +2433,9 @@ class CopienatorApp(tk.Tk):
self.tree.see("correction")
def _on_close(self) -> None:
if self.web_session is not None:
messagebox.showinfo("Finalisation web", "Arrêtez le serveur depuis la fenêtre de finalisation avant de fermer le GUI.")
return
if self.runner.running:
if not messagebox.askyesno(
"Traitement en cours", "Un script est encore actif. Le forcer à s’arrêter et fermer l’application ?"