feat(ui,api): wave 3 — editorial pricing, real cancel, server history, DeepL purge
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3m36s

Pricing: full editorial redesign — serif card headers with accent pills
replace the colored font-black blocks, tone sweep across toggle/metrics/
features/CTAs, PLAN_COLORS removed; one design system app-wide.

Translate: decorative titles one step down (CTA hierarchy restored);
glossary and image-translation blocks hidden entirely for free users
(progressive disclosure — three controls for free).

Reviews: XLIFF hint line explains the exchange format; backend errors
routed through a friendly mapper (session/not-found/rate-limit/server).

Landing: fabricated hero UI cards (fake 'Context Engine' overlay)
removed — the photo no longer promises screens that don't exist.

Nav: single DashboardNavLinks component shared by sidebar and mobile
drawer (was duplicated markup).

API: GET /api/v1/translations (user job history, paginated; completed
jobs retained 24h) and POST /api/v1/translations/{id}/cancel —
cooperative cancellation with worker checkpoints before dispatch and
before finalisation, reserved quota released immediately. Translate
monitor now offers a real 'Cancel translation' next to 'Back to start';
recent-jobs list reads server history first, localStorage fallback.

DeepL purge (backend): provider module, registry registration, config
attrs/defaults, dispatch branch, admin settings schema + test branch,
legacy availability block, validation rules, plan provider lists,
error-code mappings, MCP enums, translator prompt mention, related
tests updated/removed. Fallback resolver skips unknown providers, so
stale chains containing 'deepl' degrade gracefully.

Verified: backend 110 tests passed; frontend build exit 0, vitest 9/9,
0 missing i18n keys, eslint 63 errors (vs 64 at HEAD).
This commit is contained in:
2026-08-30 22:42:29 +02:00
parent 52748ee653
commit 67365918ae
35 changed files with 398 additions and 1572 deletions

View File

@@ -337,7 +337,6 @@ async def get_admin_dashboard(admin_id: str = Depends(require_admin)):
"label": label,
}
_engine_status("deepl", "DEEPL_API_KEY", "DeepL")
_engine_status("openrouter", "OPENROUTER_API_KEY", "Traduction IA Éco")
_engine_status("openrouter_premium", "OPENROUTER_API_KEY", "Traduction IA Premium")
_engine_status("openai", "OPENAI_API_KEY", "OpenAI")
@@ -712,7 +711,6 @@ async def update_default_provider(
valid_providers = [
"google",
"google_cloud",
"deepl",
"openai",
"openrouter",
"openrouter_premium",
@@ -916,7 +914,6 @@ class SmtpSettings(BaseModel):
class SettingsConfig(BaseModel):
google: ProviderSettings = ProviderSettings(enabled=True)
google_cloud: ProviderSettings = ProviderSettings() # Cloud Translation API v2 (clé API)
deepl: ProviderSettings = ProviderSettings()
openai: ProviderSettings = ProviderSettings()
openrouter: ProviderSettings = ProviderSettings() # "Traduction IA Essentielle"
@@ -926,8 +923,8 @@ class SettingsConfig(BaseModel):
zai: ProviderSettings = ProviderSettings()
mistral: ProviderSettings = ProviderSettings() # OCR Mistral (PDF scannés)
smtp: SmtpSettings = SmtpSettings()
fallback_chain: str = "google,google_cloud,deepl,openrouter,openrouter_premium,openai,deepseek,zai"
fallback_chain_classic: str = "google,google_cloud,deepl"
fallback_chain: str = "google,google_cloud,openrouter,openrouter_premium,openai,deepseek,zai"
fallback_chain_classic: str = "google,google_cloud"
fallback_chain_llm: str = "openrouter,openrouter_premium,openai,deepseek,zai"
@@ -991,7 +988,6 @@ async def get_settings(admin_id: str = Depends(require_admin)):
# Premium : Claude Sonnet 4.6 — précision maximale sur documents complexes
payload["openrouter_premium"] = _merge_env(settings.openrouter_premium, key_env="OPENROUTER_API_KEY", model_env="OPENROUTER_PREMIUM_MODEL", default_model="anthropic/claude-sonnet-4.6")
payload["openai"] = _merge_env(settings.openai, key_env="OPENAI_API_KEY", model_env="OPENAI_MODEL", default_model="gpt-4o-mini")
payload["deepl"] = _merge_env(settings.deepl, key_env="DEEPL_API_KEY")
payload["deepseek"] = _merge_env(settings.deepseek, key_env="DEEPSEEK_API_KEY", model_env="DEEPSEEK_MODEL", default_model="deepseek-chat")
payload["minimax"] = _merge_env(settings.minimax, key_env="MINIMAX_API_KEY", model_env="MINIMAX_MODEL", default_model="abab6.5s-chat")
payload["zai"] = _merge_env(settings.zai, key_env="ZAI_API_KEY", model_env="ZAI_MODEL", url_env="ZAI_BASE_URL", default_model="grok-2-1212", default_url="https://api.x.ai/v1")
@@ -1023,7 +1019,6 @@ async def get_settings(admin_id: str = Depends(require_admin)):
# (boolean only — never expose actual values)
has_openrouter = bool(os.getenv("OPENROUTER_API_KEY", "").strip())
env_info = {
"deepl": bool(os.getenv("DEEPL_API_KEY", "").strip()),
"openai": bool(os.getenv("OPENAI_API_KEY", "").strip()),
"openrouter": has_openrouter,
"openrouter_premium": has_openrouter, # same key, different model
@@ -1193,20 +1188,6 @@ async def test_provider(
},
)
elif provider == "deepl":
api_key = _key(provider_config.api_key, "DEEPL_API_KEY")
if not api_key:
return JSONResponse(
status_code=400,
content={"available": False, "error": "Aucune clé API DeepL trouvée (JSON ou .env)"},
)
import deepl
translator = deepl.Translator(api_key)
usage = translator.get_usage()
return JSONResponse(
status_code=200, content={"available": True, "usage": str(usage)}
)
elif provider == "openrouter_premium":
current.openrouter_premium = _update_provider(current.openrouter_premium, update_data)