feat: track user interest in paid plans and enhance admin analytics

- Added tracking for user interest in the pro plan on the PricingPage.
- Implemented auto-retry for CSRF token failures in API response interceptor.
- Introduced new interfaces and API functions for enhanced admin analytics, including ratings, tool usage, user stats, plan interest, and system health.
This commit is contained in:
Your Name
2026-03-21 13:49:02 +02:00
parent c800f707e3
commit d8a51d8494
9 changed files with 2027 additions and 406 deletions

View File

@@ -55,8 +55,9 @@ def _call_openrouter(
settings = get_openrouter_settings()
if not settings.api_key:
logger.error("OPENROUTER_API_KEY is not set or is a placeholder value.")
raise PdfAiError(
"AI service is not configured. Set OPENROUTER_API_KEY in the application configuration."
"AI features are temporarily unavailable. Our team has been notified."
)
messages = [
@@ -79,9 +80,40 @@ def _call_openrouter(
},
timeout=60,
)
if response.status_code == 401:
logger.error("OpenRouter API key is invalid or expired (401).")
raise PdfAiError(
"AI features are temporarily unavailable due to a configuration issue. Our team has been notified."
)
if response.status_code == 402:
logger.error("OpenRouter account has insufficient credits (402).")
raise PdfAiError(
"AI processing credits have been exhausted. Please try again later."
)
if response.status_code == 429:
logger.warning("OpenRouter rate limit reached (429).")
raise PdfAiError(
"AI service is experiencing high demand. Please wait a moment and try again."
)
if response.status_code >= 500:
logger.error("OpenRouter server error (%s).", response.status_code)
raise PdfAiError(
"AI service provider is experiencing issues. Please try again shortly."
)
response.raise_for_status()
data = response.json()
# Handle model-level errors returned inside a 200 response
if data.get("error"):
error_msg = data["error"].get("message", "") if isinstance(data["error"], dict) else str(data["error"])
logger.error("OpenRouter returned an error payload: %s", error_msg)
raise PdfAiError("AI service encountered an issue. Please try again.")
reply = (
data.get("choices", [{}])[0]
.get("message", {})
@@ -107,10 +139,15 @@ def _call_openrouter(
return reply
except PdfAiError:
raise
except requests.exceptions.Timeout:
raise PdfAiError("AI service timed out. Please try again.")
except requests.exceptions.ConnectionError:
logger.error("Cannot connect to OpenRouter API at %s", settings.base_url)
raise PdfAiError("AI service is unreachable. Please try again shortly.")
except requests.exceptions.RequestException as e:
logger.error(f"OpenRouter API error: {e}")
logger.error("OpenRouter API error: %s", e)
raise PdfAiError("AI service is temporarily unavailable.")