Commit 2765eea2 authored by Your Name's avatar Your Name

0.99.40

parent bebce4e7
...@@ -366,6 +366,7 @@ class Analytics: ...@@ -366,6 +366,7 @@ class Analytics:
provider_id: Optional provider filter provider_id: Optional provider filter
from_datetime: Start datetime from_datetime: Start datetime
to_datetime: End datetime to_datetime: End datetime
user_filter: Optional user ID to filter by (-1 for global only)
Returns: Returns:
Dictionary with token counts and cost estimates Dictionary with token counts and cost estimates
...@@ -380,21 +381,36 @@ class Analytics: ...@@ -380,21 +381,36 @@ class Analytics:
formatted_start = self._format_timestamp(start) formatted_start = self._format_timestamp(start)
formatted_end = self._format_timestamp(end) formatted_end = self._format_timestamp(end)
# Build user condition
if user_filter == -1:
user_condition = " AND user_id IS NULL"
params_suffix = []
elif user_filter is not None:
user_condition = f" AND user_id = {placeholder}"
params_suffix = [user_filter]
else:
user_condition = ""
params_suffix = []
if provider_id: if provider_id:
params = [provider_id, formatted_start, formatted_end] + params_suffix
cursor.execute(f''' cursor.execute(f'''
SELECT SUM(tokens_used) as total_tokens SELECT SUM(tokens_used) as total_tokens
FROM token_usage FROM token_usage
WHERE provider_id = {placeholder} AND timestamp >= {placeholder} AND timestamp <= {placeholder} WHERE provider_id = {placeholder} AND timestamp >= {placeholder} AND timestamp <= {placeholder}
''', (provider_id, formatted_start, formatted_end)) {user_condition}
''', params)
row = cursor.fetchone() row = cursor.fetchone()
total_tokens = row[0] if row and row[0] else 0 total_tokens = row[0] if row and row[0] else 0
else: else:
params = [formatted_start, formatted_end] + params_suffix
cursor.execute(f''' cursor.execute(f'''
SELECT provider_id, SUM(tokens_used) as total_tokens SELECT provider_id, SUM(tokens_used) as total_tokens
FROM token_usage FROM token_usage
WHERE timestamp >= {placeholder} AND timestamp <= {placeholder} WHERE timestamp >= {placeholder} AND timestamp <= {placeholder}
{user_condition}
GROUP BY provider_id GROUP BY provider_id
''', (formatted_start, formatted_end)) ''', params)
provider_tokens = {} provider_tokens = {}
total_tokens = 0 total_tokens = 0
...@@ -931,6 +947,7 @@ class Analytics: ...@@ -931,6 +947,7 @@ class Analytics:
Args: Args:
from_datetime: Optional start datetime for filtering from_datetime: Optional start datetime for filtering
to_datetime: Optional end datetime for filtering to_datetime: Optional end datetime for filtering
user_filter: Optional user ID to filter by (-1 for global only)
Returns: Returns:
Dictionary with cost estimates Dictionary with cost estimates
...@@ -940,10 +957,10 @@ class Analytics: ...@@ -940,10 +957,10 @@ class Analytics:
end = to_datetime or datetime.now() end = to_datetime or datetime.now()
# Get token usage by date range # Get token usage by date range
range_usage = self.get_token_usage_by_date_range(None, start, end) range_usage = self.get_token_usage_by_date_range(None, start, end, user_filter)
# Get providers that have data # Get providers that have data
providers = self.get_all_providers_stats(from_datetime, to_datetime) providers = self.get_all_providers_stats(from_datetime, to_datetime, user_filter)
total_cost = 0.0 total_cost = 0.0
provider_costs = [] provider_costs = []
......
...@@ -1172,6 +1172,9 @@ class ResponseCache: ...@@ -1172,6 +1172,9 @@ class ResponseCache:
# Per-provider statistics # Per-provider statistics
self.provider_stats = {} self.provider_stats = {}
# Per-user statistics
self.user_stats = {}
# Initialize backends # Initialize backends
self.redis_client = None self.redis_client = None
self.sqlite_backend = None self.sqlite_backend = None
...@@ -1346,12 +1349,13 @@ class ResponseCache: ...@@ -1346,12 +1349,13 @@ class ResponseCache:
self._memory_cache.pop(lru_key, None) self._memory_cache.pop(lru_key, None)
self._memory_timestamps.pop(lru_key, None) self._memory_timestamps.pop(lru_key, None)
def get(self, request_data: Dict) -> Optional[Any]: def get(self, request_data: Dict, user_id: Optional[int] = None) -> Optional[Any]:
""" """
Get cached response for a request. Get cached response for a request.
Args: Args:
request_data: The request data dict request_data: The request data dict
user_id: Optional user ID for tracking statistics
Returns: Returns:
Cached response dict or None if not found Cached response dict or None if not found
...@@ -1369,6 +1373,7 @@ class ResponseCache: ...@@ -1369,6 +1373,7 @@ class ResponseCache:
if data: if data:
self.stats['hits'] += 1 self.stats['hits'] += 1
self._update_provider_stats(provider_id, 'hits') self._update_provider_stats(provider_id, 'hits')
self._update_user_stats(user_id, 'hits')
logger.debug(f"Cache hit (Redis): {cache_key}") logger.debug(f"Cache hit (Redis): {cache_key}")
return self._deserialize_response(data) return self._deserialize_response(data)
elif self.backend == 'sqlite' and self.sqlite_backend: elif self.backend == 'sqlite' and self.sqlite_backend:
...@@ -1377,6 +1382,7 @@ class ResponseCache: ...@@ -1377,6 +1382,7 @@ class ResponseCache:
if data: if data:
self.stats['hits'] += 1 self.stats['hits'] += 1
self._update_provider_stats(provider_id, 'hits') self._update_provider_stats(provider_id, 'hits')
self._update_user_stats(user_id, 'hits')
logger.debug(f"Cache hit (SQLite): {cache_key}") logger.debug(f"Cache hit (SQLite): {cache_key}")
return data return data
elif self.backend == 'mysql' and self.mysql_backend: elif self.backend == 'mysql' and self.mysql_backend:
...@@ -1385,6 +1391,7 @@ class ResponseCache: ...@@ -1385,6 +1391,7 @@ class ResponseCache:
if data: if data:
self.stats['hits'] += 1 self.stats['hits'] += 1
self._update_provider_stats(provider_id, 'hits') self._update_provider_stats(provider_id, 'hits')
self._update_user_stats(user_id, 'hits')
logger.debug(f"Cache hit (MySQL): {cache_key}") logger.debug(f"Cache hit (MySQL): {cache_key}")
return data return data
elif self.backend == 'memory': elif self.backend == 'memory':
...@@ -1407,11 +1414,13 @@ class ResponseCache: ...@@ -1407,11 +1414,13 @@ class ResponseCache:
self.stats['hits'] += 1 self.stats['hits'] += 1
self._update_provider_stats(provider_id, 'hits') self._update_provider_stats(provider_id, 'hits')
self._update_user_stats(user_id, 'hits')
logger.debug(f"Cache hit (Memory): {cache_key}") logger.debug(f"Cache hit (Memory): {cache_key}")
return self._memory_cache[cache_key] return self._memory_cache[cache_key]
self.stats['misses'] += 1 self.stats['misses'] += 1
self._update_provider_stats(provider_id, 'misses') self._update_provider_stats(provider_id, 'misses')
self._update_user_stats(user_id, 'misses')
logger.debug(f"Cache miss: {cache_key}") logger.debug(f"Cache miss: {cache_key}")
return None return None
...@@ -1420,7 +1429,7 @@ class ResponseCache: ...@@ -1420,7 +1429,7 @@ class ResponseCache:
logger.warning(f"Cache get error: {e}") logger.warning(f"Cache get error: {e}")
return None return None
def set(self, request_data: Dict, response: Any, ttl: Optional[int] = None) -> None: def set(self, request_data: Dict, response: Any, ttl: Optional[int] = None, user_id: Optional[int] = None) -> None:
""" """
Cache a response. Cache a response.
...@@ -1428,6 +1437,7 @@ class ResponseCache: ...@@ -1428,6 +1437,7 @@ class ResponseCache:
request_data: The request data dict request_data: The request data dict
response: The response to cache (dict or object) response: The response to cache (dict or object)
ttl: TTL in seconds (uses default if None) ttl: TTL in seconds (uses default if None)
user_id: Optional user ID for tracking statistics
""" """
if not self.enabled: if not self.enabled:
return return
...@@ -1473,6 +1483,7 @@ class ResponseCache: ...@@ -1473,6 +1483,7 @@ class ResponseCache:
logger.debug(f"Cached response (Memory): {cache_key} (TTL: {ttl_value}s)") logger.debug(f"Cached response (Memory): {cache_key} (TTL: {ttl_value}s)")
self.stats['sets'] += 1 self.stats['sets'] += 1
self._update_user_stats(user_id, 'sets')
except Exception as e: except Exception as e:
self.stats['errors'] += 1 self.stats['errors'] += 1
...@@ -1510,6 +1521,55 @@ class ResponseCache: ...@@ -1510,6 +1521,55 @@ class ResponseCache:
self.stats['errors'] += 1 self.stats['errors'] += 1
logger.warning(f"Cache delete error: {e}") logger.warning(f"Cache delete error: {e}")
def _update_provider_stats(self, provider_id: str, stat_type: str) -> None:
"""Update per-provider statistics"""
if provider_id not in self.provider_stats:
self.provider_stats[provider_id] = {
'hits': 0,
'misses': 0,
'sets': 0,
'deletes': 0,
'errors': 0
}
self.provider_stats[provider_id][stat_type] += 1
def _update_user_stats(self, user_id: Optional[int], stat_type: str) -> None:
"""Update per-user statistics"""
if user_id is None:
return
user_key = f"user:{user_id}"
if user_key not in self.user_stats:
self.user_stats[user_key] = {
'hits': 0,
'misses': 0,
'sets': 0,
'deletes': 0,
'errors': 0
}
self.user_stats[user_key][stat_type] += 1
def get_user_stats(self, user_id: int) -> Dict[str, Any]:
"""Get cache statistics for a specific user"""
user_key = f"user:{user_id}"
if user_key not in self.user_stats:
return {
'enabled': self.enabled,
'hits': 0,
'misses': 0,
'hit_rate': 0.0,
'total_requests': 0,
'backend': self.backend
}
stats = self.user_stats[user_key].copy()
total = stats['hits'] + stats['misses']
stats['hit_rate'] = stats['hits'] / total if total > 0 else 0.0
stats['total_requests'] = total
stats['enabled'] = self.enabled
stats['backend'] = self.backend
return stats
def clear(self) -> None: def clear(self) -> None:
"""Clear all cached responses""" """Clear all cached responses"""
if not self.enabled: if not self.enabled:
......
...@@ -345,7 +345,7 @@ class RequestHandler: ...@@ -345,7 +345,7 @@ class RequestHandler:
aisbf_config = self.config.get_aisbf_config() aisbf_config = self.config.get_aisbf_config()
if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled: if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled:
response_cache = get_response_cache(aisbf_config.response_cache.model_dump()) response_cache = get_response_cache(aisbf_config.response_cache.model_dump())
cached_response = response_cache.get(request_data) cached_response = response_cache.get(request_data, user_id=self.user_id)
if cached_response: if cached_response:
logger.info(f"Cache hit for request to provider {provider_id}") logger.info(f"Cache hit for request to provider {provider_id}")
return cached_response return cached_response
...@@ -479,7 +479,7 @@ class RequestHandler: ...@@ -479,7 +479,7 @@ class RequestHandler:
aisbf_config = self.config.get_aisbf_config() aisbf_config = self.config.get_aisbf_config()
if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled: if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled:
response_cache = get_response_cache(aisbf_config.response_cache.model_dump()) response_cache = get_response_cache(aisbf_config.response_cache.model_dump())
response_cache.set(request_data, response) response_cache.set(request_data, response, user_id=self.user_id)
logger.debug(f"Cached chunked response for request to provider {provider_id}") logger.debug(f"Cached chunked response for request to provider {provider_id}")
except Exception as cache_error: except Exception as cache_error:
logger.warning(f"Response cache set failed for chunked request: {cache_error}") logger.warning(f"Response cache set failed for chunked request: {cache_error}")
...@@ -521,7 +521,7 @@ class RequestHandler: ...@@ -521,7 +521,7 @@ class RequestHandler:
aisbf_config = self.config.get_aisbf_config() aisbf_config = self.config.get_aisbf_config()
if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled: if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled:
response_cache = get_response_cache(aisbf_config.response_cache.model_dump()) response_cache = get_response_cache(aisbf_config.response_cache.model_dump())
response_cache.set(request_data, response) response_cache.set(request_data, response, user_id=self.user_id)
logger.debug(f"Cached response for request to provider {provider_id}") logger.debug(f"Cached response for request to provider {provider_id}")
except Exception as cache_error: except Exception as cache_error:
logger.warning(f"Response cache set failed: {cache_error}") logger.warning(f"Response cache set failed: {cache_error}")
...@@ -2353,7 +2353,7 @@ class RotationHandler: ...@@ -2353,7 +2353,7 @@ class RotationHandler:
aisbf_config = self.config.get_aisbf_config() aisbf_config = self.config.get_aisbf_config()
if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled: if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled:
response_cache = get_response_cache(aisbf_config.response_cache.model_dump()) response_cache = get_response_cache(aisbf_config.response_cache.model_dump())
cached_response = response_cache.get(request_data) cached_response = response_cache.get(request_data, user_id=self.user_id)
if cached_response: if cached_response:
logger.info(f"Cache hit for rotation request {rotation_id}") logger.info(f"Cache hit for rotation request {rotation_id}")
return cached_response return cached_response
...@@ -2863,7 +2863,7 @@ class RotationHandler: ...@@ -2863,7 +2863,7 @@ class RotationHandler:
aisbf_config = self.config.get_aisbf_config() aisbf_config = self.config.get_aisbf_config()
if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled: if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled:
response_cache = get_response_cache(aisbf_config.response_cache.model_dump()) response_cache = get_response_cache(aisbf_config.response_cache.model_dump())
response_cache.set(request_data, response) response_cache.set(request_data, response, user_id=self.user_id)
logger.debug(f"Cached chunked response for rotation request {rotation_id}") logger.debug(f"Cached chunked response for rotation request {rotation_id}")
except Exception as cache_error: except Exception as cache_error:
logger.warning(f"Response cache set failed for chunked request: {cache_error}") logger.warning(f"Response cache set failed for chunked request: {cache_error}")
...@@ -2935,7 +2935,7 @@ class RotationHandler: ...@@ -2935,7 +2935,7 @@ class RotationHandler:
aisbf_config = self.config.get_aisbf_config() aisbf_config = self.config.get_aisbf_config()
if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled: if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled:
response_cache = get_response_cache(aisbf_config.response_cache.model_dump()) response_cache = get_response_cache(aisbf_config.response_cache.model_dump())
response_cache.set(request_data, response) response_cache.set(request_data, response, user_id=self.user_id)
logger.debug(f"Cached response for rotation request {rotation_id}") logger.debug(f"Cached response for rotation request {rotation_id}")
except Exception as cache_error: except Exception as cache_error:
logger.warning(f"Response cache set failed: {cache_error}") logger.warning(f"Response cache set failed: {cache_error}")
...@@ -4275,7 +4275,7 @@ class AutoselectHandler: ...@@ -4275,7 +4275,7 @@ class AutoselectHandler:
aisbf_config = self.config.get_aisbf_config() aisbf_config = self.config.get_aisbf_config()
if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled: if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled:
response_cache = get_response_cache(aisbf_config.response_cache.model_dump()) response_cache = get_response_cache(aisbf_config.response_cache.model_dump())
cached_response = response_cache.get(request_data) cached_response = response_cache.get(request_data, user_id=self.user_id)
if cached_response: if cached_response:
logger.info(f"Cache hit for autoselect request {autoselect_id}") logger.info(f"Cache hit for autoselect request {autoselect_id}")
return cached_response return cached_response
...@@ -4431,7 +4431,7 @@ class AutoselectHandler: ...@@ -4431,7 +4431,7 @@ class AutoselectHandler:
aisbf_config = self.config.get_aisbf_config() aisbf_config = self.config.get_aisbf_config()
if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled: if aisbf_config and aisbf_config.response_cache and aisbf_config.response_cache.enabled:
response_cache = get_response_cache(aisbf_config.response_cache.model_dump()) response_cache = get_response_cache(aisbf_config.response_cache.model_dump())
response_cache.set(request_data, response) response_cache.set(request_data, response, user_id=self.user_id)
logger.debug(f"Cached response for autoselect request {autoselect_id}") logger.debug(f"Cached response for autoselect request {autoselect_id}")
except Exception as cache_error: except Exception as cache_error:
logger.warning(f"Response cache set failed: {cache_error}") logger.warning(f"Response cache set failed: {cache_error}")
......
...@@ -697,24 +697,47 @@ class AdaptiveRateLimiter: ...@@ -697,24 +697,47 @@ class AdaptiveRateLimiter:
logger.info(f"[AdaptiveRateLimiter {self.provider_id}] Reset to initial state") logger.info(f"[AdaptiveRateLimiter {self.provider_id}] Reset to initial state")
# Global adaptive rate limiters registry # Global adaptive rate limiters registry - now supports user-specific limiters
# Key format: "provider_id" (global) or "user:user_id:provider_id" (user-specific)
_adaptive_rate_limiters: Dict[str, AdaptiveRateLimiter] = {} _adaptive_rate_limiters: Dict[str, AdaptiveRateLimiter] = {}
def get_adaptive_rate_limiter(provider_id: str, config: Dict = None) -> AdaptiveRateLimiter: def get_adaptive_rate_limiter(provider_id: str, config: Dict = None, user_id: Optional[int] = None) -> AdaptiveRateLimiter:
"""Get or create an adaptive rate limiter for a provider.""" """Get or create an adaptive rate limiter for a provider, optionally user-specific."""
global _adaptive_rate_limiters global _adaptive_rate_limiters
if provider_id not in _adaptive_rate_limiters: # Create key based on whether user_id is provided
_adaptive_rate_limiters[provider_id] = AdaptiveRateLimiter(provider_id, config) if user_id is not None:
key = f"user:{user_id}:{provider_id}"
else:
key = provider_id
if key not in _adaptive_rate_limiters:
_adaptive_rate_limiters[key] = AdaptiveRateLimiter(key, config)
return _adaptive_rate_limiters[provider_id] return _adaptive_rate_limiters[key]
def get_all_adaptive_rate_limiters() -> Dict[str, AdaptiveRateLimiter]: def get_all_adaptive_rate_limiters(user_id: Optional[int] = None) -> Dict[str, AdaptiveRateLimiter]:
"""Get all adaptive rate limiters.""" """Get all adaptive rate limiters, optionally filtered by user."""
global _adaptive_rate_limiters global _adaptive_rate_limiters
if user_id is None:
# Return all limiters (admin view)
return _adaptive_rate_limiters return _adaptive_rate_limiters
else:
# Return only limiters for this user + global ones
user_prefix = f"user:{user_id}:"
filtered = {}
for key, limiter in _adaptive_rate_limiters.items():
if key.startswith(user_prefix) or not key.startswith("user:"):
# Strip the user prefix for display to user
if key.startswith(user_prefix):
display_key = key[len(user_prefix):]
else:
display_key = key
filtered[display_key] = limiter
return filtered
class BaseProviderHandler: class BaseProviderHandler:
...@@ -756,7 +779,7 @@ class BaseProviderHandler: ...@@ -756,7 +779,7 @@ class BaseProviderHandler:
adaptive_config = None adaptive_config = None
if config.aisbf and config.aisbf.adaptive_rate_limiting: if config.aisbf and config.aisbf.adaptive_rate_limiting:
adaptive_config = config.aisbf.adaptive_rate_limiting.dict() adaptive_config = config.aisbf.adaptive_rate_limiting.dict()
self.adaptive_limiter = get_adaptive_rate_limiter(provider_id, adaptive_config) self.adaptive_limiter = get_adaptive_rate_limiter(provider_id, adaptive_config, user_id)
def parse_429_response(self, response_data: Union[Dict, str], headers: Dict = None) -> Optional[int]: def parse_429_response(self, response_data: Union[Dict, str], headers: Dict = None) -> Optional[int]:
""" """
......
...@@ -22,7 +22,12 @@ ...@@ -22,7 +22,12 @@
<!-- Cache Statistics --> <!-- Cache Statistics -->
<div style="background: #1a1a2e; padding: 20px; border-radius: 8px; margin-bottom: 30px;"> <div style="background: #1a1a2e; padding: 20px; border-radius: 8px; margin-bottom: 30px;">
<h3 style="margin-bottom: 20px;">Cache Statistics</h3> {% if is_admin %}
<h3 style="margin-bottom: 20px;">Global Cache Statistics</h3>
{% else %}
<h3 style="margin-bottom: 20px;">Your Personal Cache Impact</h3>
<p style="color: #a0a0a0; margin-bottom: 20px;">These statistics show how the response cache has benefited your requests personally.</p>
{% endif %}
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px;">
<div style="background: #0f3460; padding: 15px; border-radius: 8px;"> <div style="background: #0f3460; padding: 15px; border-radius: 8px;">
...@@ -32,26 +37,29 @@ ...@@ -32,26 +37,29 @@
</div> </div>
</div> </div>
{% if is_admin %}
<div style="background: #0f3460; padding: 15px; border-radius: 8px;"> <div style="background: #0f3460; padding: 15px; border-radius: 8px;">
<div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Backend</div> <div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Backend</div>
<div style="font-size: 24px; font-weight: bold;">{{ stats.backend|upper }}</div> <div style="font-size: 24px; font-weight: bold;">{{ stats.backend|upper }}</div>
</div> </div>
{% endif %}
<div style="background: #0f3460; padding: 15px; border-radius: 8px;"> <div style="background: #0f3460; padding: 15px; border-radius: 8px;">
<div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Cache Hits</div> <div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">{% if is_admin %}Cache Hits{% else %}Your Cache Hits{% endif %}</div>
<div style="font-size: 24px; font-weight: bold; color: #4ade80;">{{ stats.hits }}</div> <div style="font-size: 24px; font-weight: bold; color: #4ade80;">{{ stats.hits }}</div>
</div> </div>
<div style="background: #0f3460; padding: 15px; border-radius: 8px;"> <div style="background: #0f3460; padding: 15px; border-radius: 8px;">
<div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Cache Misses</div> <div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">{% if is_admin %}Cache Misses{% else %}Your Cache Misses{% endif %}</div>
<div style="font-size: 24px; font-weight: bold; color: #ef4444;">{{ stats.misses }}</div> <div style="font-size: 24px; font-weight: bold; color: #ef4444;">{{ stats.misses }}</div>
</div> </div>
<div style="background: #0f3460; padding: 15px; border-radius: 8px;"> <div style="background: #0f3460; padding: 15px; border-radius: 8px;">
<div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Hit Rate</div> <div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">{% if is_admin %}Hit Rate{% else %}Your Hit Rate{% endif %}</div>
<div style="font-size: 24px; font-weight: bold; color: #60a5fa;">{{ "%.1f"|format(stats.hit_rate * 100) }}%</div> <div style="font-size: 24px; font-weight: bold; color: #60a5fa;">{{ "%.1f"|format(stats.hit_rate * 100) }}%</div>
</div> </div>
{% if is_admin %}
<div style="background: #0f3460; padding: 15px; border-radius: 8px;"> <div style="background: #0f3460; padding: 15px; border-radius: 8px;">
<div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Cache Size</div> <div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Cache Size</div>
<div style="font-size: 24px; font-weight: bold;">{{ stats.size }}</div> <div style="font-size: 24px; font-weight: bold;">{{ stats.size }}</div>
...@@ -61,9 +69,16 @@ ...@@ -61,9 +69,16 @@
<div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Evictions</div> <div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Evictions</div>
<div style="font-size: 24px; font-weight: bold; color: #f59e0b;">{{ stats.evictions }}</div> <div style="font-size: 24px; font-weight: bold; color: #f59e0b;">{{ stats.evictions }}</div>
</div> </div>
{% else %}
<div style="background: #0f3460; padding: 15px; border-radius: 8px;">
<div style="color: #a0a0a0; font-size: 14px; margin-bottom: 5px;">Total Requests</div>
<div style="font-size: 24px; font-weight: bold; color: #a78bfa;">{{ stats.total_requests }}</div>
</div>
{% endif %}
</div> </div>
</div> </div>
{% if is_admin %}
<!-- Cache Actions --> <!-- Cache Actions -->
<div style="background: #1a1a2e; padding: 20px; border-radius: 8px; margin-bottom: 30px;"> <div style="background: #1a1a2e; padding: 20px; border-radius: 8px; margin-bottom: 30px;">
<h3 style="margin-bottom: 20px;">Cache Actions</h3> <h3 style="margin-bottom: 20px;">Cache Actions</h3>
...@@ -78,6 +93,7 @@ ...@@ -78,6 +93,7 @@
</button> </button>
</div> </div>
</div> </div>
{% endif %}
<!-- Error Display --> <!-- Error Display -->
{% if stats.error %} {% if stats.error %}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment