Improve translation reliability and progress feedback

This commit is contained in:
burnintuna 2026-07-30 17:07:02 +09:00
parent 9439858b11
commit 51a6e845d1
20 changed files with 1194 additions and 216 deletions

View file

@ -7,8 +7,12 @@ from typing import Any
from backend.models import TranslationSession
class SessionConflictError(Exception):
"""Raised when a long-running phase tries to update a changed session."""
class SessionStore:
"""Thread-safe-ish in-memory dict for translation sessions.
"""Process-local in-memory store for user-owned translation sessions.
Each session is keyed by a UUID and has a 24-hour TTL.
Expired sessions are lazily cleaned on access.
@ -18,18 +22,26 @@ class SessionStore:
self._store: dict[str, dict[str, Any]] = {}
self.ttl_hours = ttl_hours
def create(self, session_data: TranslationSession) -> str:
def create(self, session_data: TranslationSession, owner_id: str) -> str:
session_id = uuid.uuid4().hex[:16]
self._store[session_id] = {
"data": session_data.model_dump(),
"owner_id": owner_id,
"version": 0,
"created_at": datetime.now(timezone.utc),
"updated_at": datetime.now(timezone.utc),
}
return session_id
def get(self, session_id: str) -> TranslationSession | None:
def get(self, session_id: str, owner_id: str) -> TranslationSession | None:
result = self.get_with_version(session_id, owner_id)
return result[0] if result else None
def get_with_version(
self, session_id: str, owner_id: str
) -> tuple[TranslationSession, int] | None:
entry = self._store.get(session_id)
if entry is None:
if entry is None or entry["owner_id"] != owner_id:
return None
# Check TTL
now = datetime.now(timezone.utc)
@ -39,22 +51,51 @@ class SessionStore:
return None
# Touch (refresh TTL)
entry["updated_at"] = now
return TranslationSession(**entry["data"])
return TranslationSession(**entry["data"]), entry["version"]
def update(self, session_id: str, data: dict[str, Any]) -> TranslationSession | None:
def update(
self,
session_id: str,
data: dict[str, Any],
owner_id: str,
expected_version: int | None = None,
) -> TranslationSession | None:
entry = self._store.get(session_id)
if entry is None:
if entry is None or entry["owner_id"] != owner_id:
return None
now = datetime.now(timezone.utc)
if now - entry["updated_at"] > timedelta(hours=self.ttl_hours):
del self._store[session_id]
return None
if expected_version is not None and entry["version"] != expected_version:
raise SessionConflictError
entry["data"].update(data)
entry["updated_at"] = datetime.now(timezone.utc)
entry["version"] += 1
entry["updated_at"] = now
return TranslationSession(**entry["data"])
def delete(self, session_id: str) -> bool:
if session_id in self._store:
def delete(self, session_id: str, owner_id: str) -> bool:
entry = self._store.get(session_id)
if entry is not None and entry["owner_id"] == owner_id:
del self._store[session_id]
return True
return False
def set_progress(
self, session_id: str, progress: dict[str, Any] | None, owner_id: str
) -> bool:
"""Update transient progress without changing the semantic session version."""
entry = self._store.get(session_id)
if entry is None or entry["owner_id"] != owner_id:
return False
now = datetime.now(timezone.utc)
if now - entry["updated_at"] > timedelta(hours=self.ttl_hours):
del self._store[session_id]
return False
entry["data"]["progress"] = progress
entry["updated_at"] = now
return True
def cleanup_expired(self):
"""Remove all expired sessions."""
now = datetime.now(timezone.utc)