feat: update to June 2026 models (Claude Sonnet 4.6, Gemini 3.5 Flash), add glossary button, and implement cost factor quota & vision fallback
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m4s

This commit is contained in:
2026-06-14 11:05:53 +02:00
parent 5fd087979b
commit 136d40c7d8
10 changed files with 145 additions and 74 deletions

View File

@@ -45,7 +45,7 @@ from config import config
from translators import ExcelTranslator, WordTranslator, PowerPointTranslator
from models.subscription import PlanType
from middleware.tier_quota import tier_quota_service
from services.auth_service import record_usage
from services.auth_service import record_usage, check_usage_limits
from middleware.validation import FileValidator, ValidationError, LanguageValidator, webhook_validator
from middleware.api_key_auth import get_authenticated_user, get_user_from_api_key
from utils import file_handler
@@ -135,9 +135,11 @@ file_handler_util = FileHandler()
def _tier_for_quota(plan) -> str:
"""Map plan to quota tier: pro (and equivalent) = unlimited, else free."""
"""Map plan to quota tier: pro/starter (and equivalent) = unlimited, else free."""
if plan in (PlanType.PRO, PlanType.BUSINESS, PlanType.ENTERPRISE):
return "pro"
if plan == PlanType.STARTER:
return "starter"
return "free"
@@ -630,6 +632,22 @@ async def translate_document_v1(
},
headers={"Retry-After": str(retry_after)},
)
# Strict database plan limit check (Starter, Pro, Business, Enterprise)
usage = check_usage_limits(current_user)
if not usage["can_translate"]:
raise HTTPException(
status_code=429,
detail={
"error": "QUOTA_EXCEEDED",
"message": f"Monthly limit reached ({usage['docs_used']}/{usage['docs_limit']} documents). Upgrade your plan for more.",
"details": {
"current_usage": usage["docs_used"],
"limit": usage["docs_limit"],
"tier": tier,
},
},
)
rate_limit_remaining = quota.remaining
else:
rate_limit_remaining = -1
@@ -993,7 +1011,9 @@ async def _run_translation_job(
return (admin_val or "").strip() or os.getenv(env_var, default)
api_key = _cfg(_admin_cfg.openrouter.api_key, "OPENROUTER_API_KEY")
model = _cfg(_admin_cfg.openrouter.model, "OPENROUTER_MODEL", "deepseek/deepseek-v3.2")
model = _cfg(_admin_cfg.openrouter.model, "OPENROUTER_MODEL", "google/gemini-3.5-flash")
if model in ("deepseek/deepseek-v3.2", "google/gemini-2.0-flash-001"):
model = "google/gemini-3.5-flash"
# Story 3.10: Retrieve and format glossary terms for LLM prompt
glossary_terms = None
@@ -1075,7 +1095,7 @@ async def _run_translation_job(
)
elif _p == "openrouter_premium":
premium_key = _cfg(_admin_cfg.openrouter_premium.api_key, "OPENROUTER_API_KEY")
premium_model = _cfg(_admin_cfg.openrouter_premium.model, "OPENROUTER_PREMIUM_MODEL", "anthropic/claude-3.5-haiku")
premium_model = _cfg(_admin_cfg.openrouter_premium.model, "OPENROUTER_PREMIUM_MODEL", "anthropic/claude-sonnet-4.6")
if not premium_key:
premium_key = api_key # fall back to main openrouter key
if premium_key:
@@ -1096,8 +1116,15 @@ async def _run_translation_job(
)
elif _p == "deepseek":
ds_key = _cfg(getattr(_admin_cfg, "deepseek", None) and _admin_cfg.deepseek.api_key, "DEEPSEEK_API_KEY")
ds_model = _cfg(getattr(_admin_cfg, "deepseek", None) and _admin_cfg.deepseek.model, "DEEPSEEK_MODEL", "deepseek-chat")
if ds_key:
ds_model = _cfg(getattr(_admin_cfg, "deepseek", None) and _admin_cfg.deepseek.model, "DEEPSEEK_MODEL", "deepseek/deepseek-chat")
if not ds_key and api_key:
translation_provider = OpenAITranslationProvider(
api_key=api_key,
model=ds_model,
base_url="https://openrouter.ai/api/v1",
timeout=int(os.getenv("OPENROUTER_TIMEOUT", "60")),
)
elif ds_key:
translation_provider = DeepSeekTranslationProvider(
api_key=ds_key,
model=ds_model,
@@ -1295,13 +1322,30 @@ async def _run_translation_job(
)
if user_id:
await tier_quota_service.increment_on_success(user_id)
# Determine cost factor based on selected provider and model
cost_factor = 1
provider_lower = (provider or "").lower()
prov_model = ""
if translation_provider:
prov_model = getattr(translation_provider, "model", "") or ""
prov_model_lower = prov_model.lower()
if any(k in prov_model_lower for k in ["claude", "fable", "gpt-4"]) or provider_lower == "openrouter_premium":
if "haiku" in prov_model_lower:
cost_factor = 1
else:
cost_factor = 5
for _ in range(cost_factor):
await tier_quota_service.increment_on_success(user_id)
# Persist monthly usage counters in PostgreSQL (docs + pages)
pages = await asyncio.to_thread(
_estimate_pages, input_path, file_extension
)
await asyncio.to_thread(record_usage, user_id, pages)
logger.info(f"Job {job_id}: usage recorded — {pages} page(s)")
await asyncio.to_thread(record_usage, user_id, pages, False, cost_factor)
logger.info(f"Job {job_id}: usage recorded — {pages} page(s) with cost factor {cost_factor}")
# Apply watermark for Free-tier users
plan_name = (user_plan or "free").lower()