diff --git a/app/main.py b/app/main.py
index cb7d236..4ce65a9 100644
--- a/app/main.py
+++ b/app/main.py
@@ -375,6 +375,31 @@ async def view_job(job_id: int):
return {"url": f"http://{worker['host']}:{port}"}
+@app.post("/jobs/{job_id}/live")
+async def live_job(job_id: int):
+ """Return the URL of demo.py's native viser (PointCloudViewer with camera frustums,
+ confidence filtering, animation) if it's still listening. Otherwise 404 so the UI falls
+ back to /view (viser_ply.py standalone)."""
+ with closing(db()) as conn:
+ row = conn.execute(
+ "SELECT viser_url, worker_host FROM jobs WHERE id=? AND status='done'",
+ (job_id,),
+ ).fetchone()
+ if not row or not row["viser_url"]:
+ raise HTTPException(404, "viser natif jamais démarré")
+ worker = _worker_by_host(row["worker_host"]) or WORKERS[0]
+ native_port = worker["viser_port_base"] + job_id
+ proc = await asyncio.create_subprocess_exec(
+ "ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=3", worker["ssh_alias"],
+ f"nc -z -w2 127.0.0.1 {native_port}",
+ stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL,
+ )
+ await proc.wait()
+ if proc.returncode != 0:
+ raise HTTPException(410, "viser natif fermé — utilise le bouton PLY")
+ return {"url": row["viser_url"]}
+
+
@app.post("/stitches/{stitch_id}/view")
async def view_stitch(stitch_id: int):
with closing(db()) as conn:
diff --git a/app/templates/_jobs_table.html b/app/templates/_jobs_table.html
index 1c748ce..be9911a 100644
--- a/app/templates/_jobs_table.html
+++ b/app/templates/_jobs_table.html
@@ -23,7 +23,8 @@
{{ j.progress }}%
{% endif %}
{% if j.status == 'done' and j.ply_path %}
-
+
+
{% endif %}
{{ j._duration }}