feat(translation): live re-translate progress in the Settings card
translation/status now reports `active` (a translate/retranslate sweep is running, from the TaskRun table) and `last_run` (the most recent finished run's task + status). The Settings card polls live while a sweep runs, showing a spinner + "Translating… N remaining" that ticks down, and flags a last run that ended in error/timeout. No migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ from ..models import (
|
||||
ImportTask,
|
||||
Post,
|
||||
Tag,
|
||||
TaskRun,
|
||||
)
|
||||
from ..services import interpreter_client as ic
|
||||
|
||||
@@ -306,6 +307,10 @@ async def translation_status():
|
||||
"""For the Settings card: is it on, is a URL set, is the service reachable,
|
||||
and how many posts still await translation. Health runs the sync client in a
|
||||
thread so the event loop isn't blocked."""
|
||||
translation_tasks = (
|
||||
"backend.app.tasks.translation.translate_posts",
|
||||
"backend.app.tasks.translation.retranslate_posts",
|
||||
)
|
||||
async with get_session() as session:
|
||||
cfg = await ImportSettings.load(session)
|
||||
untranslated = (await session.execute(
|
||||
@@ -315,6 +320,21 @@ async def translation_status():
|
||||
Post.post_title.is_not(None), Post.description.is_not(None),
|
||||
))
|
||||
)).scalar_one()
|
||||
# Live progress: is a sweep running now, and what did the last one do?
|
||||
# (run-until-done re-enqueues itself, so `active` stays true across a
|
||||
# bulk re-translate; `last_run` surfaces a completed run's outcome.)
|
||||
active = (await session.execute(
|
||||
select(func.count(TaskRun.id))
|
||||
.where(TaskRun.task_name.in_(translation_tasks))
|
||||
.where(TaskRun.status == "running")
|
||||
)).scalar_one()
|
||||
last = (await session.execute(
|
||||
select(TaskRun.task_name, TaskRun.status, TaskRun.finished_at)
|
||||
.where(TaskRun.task_name.in_(translation_tasks))
|
||||
.where(TaskRun.finished_at.is_not(None))
|
||||
.order_by(TaskRun.finished_at.desc())
|
||||
.limit(1)
|
||||
)).first()
|
||||
base_url = (cfg.interpreter_base_url or "").strip()
|
||||
healthy = await asyncio.to_thread(ic.health, base_url) if base_url else False
|
||||
return jsonify({
|
||||
@@ -322,6 +342,12 @@ async def translation_status():
|
||||
"base_url_set": bool(base_url),
|
||||
"healthy": healthy,
|
||||
"untranslated_count": int(untranslated),
|
||||
"active": int(active) > 0,
|
||||
"last_run": {
|
||||
"task": last[0].rsplit(".", 1)[-1],
|
||||
"status": last[1],
|
||||
"finished_at": last[2].isoformat() if last[2] else None,
|
||||
} if last else None,
|
||||
})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user