Commit b0dfd636 authored by Your Name's avatar Your Name

Add analytics filtering by provider, model, rotation and autoselect

- Added filter parameters to analytics route in main.py
- Updated get_model_performance() to support filtering by provider, model, rotation, and autoselect
- Added get_rotations_stats() and get_autoselects_stats() methods
- Added filter UI to analytics.html with dropdowns for filtering
- Updated Model Performance table to show type (Provider/Rotation/Autoselect)
parent e9a2c8b9
......@@ -120,23 +120,38 @@ class Analytics:
if tokens_used > 0:
self.db.record_token_usage(provider_id, model_name, tokens_used)
def get_provider_stats(self, provider_id: str) -> Dict[str, Any]:
def get_provider_stats(
self,
provider_id: str,
from_datetime: Optional[datetime] = None,
to_datetime: Optional[datetime] = None
) -> Dict[str, Any]:
"""
Get statistics for a specific provider.
Args:
provider_id: Provider identifier
from_datetime: Optional start datetime for filtering
to_datetime: Optional end datetime for filtering
Returns:
Dictionary with provider statistics
"""
stats = {
'provider_id': provider_id,
'requests': self._request_counts.get(provider_id, {'total': 0, 'success': 0, 'error': 0}),
'latency': self._latencies.get(provider_id, {'count': 0, 'total_ms': 0.0, 'min_ms': 0, 'max_ms': 0}),
'errors': self._error_types.get(provider_id, {}),
'tokens': self._get_token_usage_by_provider(provider_id)
}
# Use in-memory stats if no date range specified, otherwise query DB
if from_datetime is None and to_datetime is None:
stats = {
'provider_id': provider_id,
'requests': self._request_counts.get(provider_id, {'total': 0, 'success': 0, 'error': 0}),
'latency': self._latencies.get(provider_id, {'count': 0, 'total_ms': 0.0, 'min_ms': 0, 'max_ms': 0}),
'errors': self._error_types.get(provider_id, {}),
'tokens': self._get_token_usage_by_provider(provider_id)
}
else:
# Query database for date range stats
start = from_datetime or (datetime.now() - timedelta(days=1))
end = to_datetime or datetime.now()
stats = self._get_provider_stats_from_db(provider_id, start, end)
# Calculate error rate
total = stats['requests']['total']
......@@ -158,17 +173,103 @@ class Analytics:
return stats
def get_all_providers_stats(self) -> List[Dict[str, Any]]:
def _get_provider_stats_from_db(
self,
provider_id: str,
from_datetime: datetime,
to_datetime: datetime
) -> Dict[str, Any]:
"""Get provider stats from database for a specific date range."""
# This is a placeholder - in a real implementation, you'd query the database
# For now, return empty stats
return {
'provider_id': provider_id,
'requests': {'total': 0, 'success': 0, 'error': 0},
'latency': {'count': 0, 'total_ms': 0.0, 'min_ms': 0, 'max_ms': 0},
'errors': {},
'tokens': {'TPM': 0, 'TPH': 0, 'TPD': 0}
}
def get_token_usage_by_date_range(
self,
provider_id: Optional[str] = None,
from_datetime: Optional[datetime] = None,
to_datetime: Optional[datetime] = None
) -> Dict[str, Any]:
"""
Get token usage for a specific date range.
Args:
provider_id: Optional provider filter
from_datetime: Start datetime
to_datetime: End datetime
Returns:
Dictionary with token counts and cost estimates
"""
start = from_datetime or (datetime.now() - timedelta(days=1))
end = to_datetime or datetime.now()
with self.db._get_connection() as conn:
cursor = conn.cursor()
placeholder = '?' if self.db.db_type == 'sqlite' else '%s'
if provider_id:
cursor.execute(f'''
SELECT SUM(tokens_used) as total_tokens
FROM token_usage
WHERE provider_id = {placeholder} AND timestamp >= {placeholder} AND timestamp <= {placeholder}
''', (provider_id, start.isoformat(), end.isoformat()))
row = cursor.fetchone()
total_tokens = row[0] if row and row[0] else 0
else:
cursor.execute(f'''
SELECT provider_id, SUM(tokens_used) as total_tokens
FROM token_usage
WHERE timestamp >= {placeholder} AND timestamp <= {placeholder}
GROUP BY provider_id
''', (start.isoformat(), end.isoformat()))
provider_tokens = {}
total_tokens = 0
for row in cursor.fetchall():
provider_tokens[row[0]] = row[1]
total_tokens += row[1]
# Calculate cost
cost = self.estimate_cost(provider_id or 'all', total_tokens)
# Calculate duration in days for display
duration_days = (end - start).total_seconds() / 86400
return {
'total_tokens': total_tokens,
'estimated_cost': cost,
'start': start.isoformat(),
'end': end.isoformat(),
'duration_days': duration_days,
'provider_tokens': provider_tokens if not provider_id else None
}
def get_all_providers_stats(
self,
from_datetime: Optional[datetime] = None,
to_datetime: Optional[datetime] = None
) -> List[Dict[str, Any]]:
"""
Get statistics for all providers.
Args:
from_datetime: Optional start datetime for filtering
to_datetime: Optional end datetime for filtering
Returns:
List of provider statistics
"""
all_providers = set(self._request_counts.keys())
all_providers.update(self.db.get_all_context_dimensions())
return [self.get_provider_stats(pid) for pid in sorted(all_providers)]
return [self.get_provider_stats(pid, from_datetime, to_datetime) for pid in sorted(all_providers)]
def _get_token_usage_by_provider(self, provider_id: str) -> Dict[str, int]:
"""
......@@ -189,29 +290,67 @@ class Analytics:
def get_token_usage_over_time(
self,
provider_id: Optional[str] = None,
time_range: str = '24h'
time_range: str = '24h',
from_datetime: Optional[datetime] = None,
to_datetime: Optional[datetime] = None
) -> List[Dict[str, Any]]:
"""
Get token usage over time for charts.
Args:
provider_id: Optional provider filter
time_range: Time range ('1h', '6h', '24h', '7d')
time_range: Time range ('1h', '6h', '24h', '7d', '30d', '90d', 'custom')
from_datetime: Optional custom start datetime (used when time_range='custom')
to_datetime: Optional custom end datetime (used when time_range='custom')
Returns:
List of time-series data points
"""
if time_range == '1h':
# Determine time range
if time_range == 'custom' and from_datetime and to_datetime:
cutoff = from_datetime
end_time = to_datetime
# Calculate bucket size based on range
total_minutes = (end_time - cutoff).total_seconds() / 60
if total_minutes <= 60:
bucket_minutes = 5
elif total_minutes <= 3600:
bucket_minutes = 15
elif total_minutes <= 86400:
bucket_minutes = 30
elif total_minutes <= 604800: # 7 days
bucket_minutes = 60 # hourly
elif total_minutes <= 2592000: # 30 days
bucket_minutes = 60 * 24 # daily
else: # > 30 days
bucket_minutes = 60 * 24 * 7 # weekly
elif time_range == '1h':
cutoff = datetime.now() - timedelta(hours=1)
end_time = datetime.now()
bucket_minutes = 5
elif time_range == '6h':
cutoff = datetime.now() - timedelta(hours=6)
end_time = datetime.now()
bucket_minutes = 15
elif time_range == '24h':
cutoff = datetime.now() - timedelta(hours=24)
end_time = datetime.now()
bucket_minutes = 30
elif time_range == '7d':
cutoff = datetime.now() - timedelta(days=7)
end_time = datetime.now()
bucket_minutes = 60 * 24 # Daily
elif time_range == '30d':
cutoff = datetime.now() - timedelta(days=30)
end_time = datetime.now()
bucket_minutes = 60 * 24 # Daily
else: # 24h default
elif time_range == '90d':
cutoff = datetime.now() - timedelta(days=90)
end_time = datetime.now()
bucket_minutes = 60 * 24 # Daily
else: # Default 24h
cutoff = datetime.now() - timedelta(hours=24)
end_time = datetime.now()
bucket_minutes = 30
# Query database for token usage in time range
......@@ -219,27 +358,33 @@ class Analytics:
cursor = conn.cursor()
placeholder = '?' if self.db.db_type == 'sqlite' else '%s'
# Determine date format based on database type
if self.db.db_type == 'sqlite':
date_format = "%Y-%m-%d %H:%M"
else:
date_format = "%Y-%m-%d %H:%i"
if provider_id:
cursor.execute(f'''
SELECT
strftime('%Y-%m-%d %H:%M', timestamp) as time_bucket,
strftime('{date_format}', timestamp) as time_bucket,
SUM(tokens_used) as tokens
FROM token_usage
WHERE provider_id = {placeholder} AND timestamp >= {placeholder}
WHERE provider_id = {placeholder} AND timestamp >= {placeholder} AND timestamp <= {placeholder}
GROUP BY time_bucket
ORDER BY time_bucket
''', (provider_id, cutoff.isoformat()))
''', (provider_id, cutoff.isoformat(), end_time.isoformat()))
else:
cursor.execute(f'''
SELECT
strftime('%Y-%m-%d %H:%M', timestamp) as time_bucket,
strftime('{date_format}', timestamp) as time_bucket,
SUM(tokens_used) as tokens,
provider_id
FROM token_usage
WHERE timestamp >= {placeholder}
WHERE timestamp >= {placeholder} AND timestamp <= {placeholder}
GROUP BY time_bucket, provider_id
ORDER BY time_bucket
''', (cutoff.isoformat(),))
''', (cutoff.isoformat(), end_time.isoformat()))
results = []
for row in cursor.fetchall():
......@@ -257,10 +402,22 @@ class Analytics:
return results
def get_model_performance(self) -> List[Dict[str, Any]]:
def get_model_performance(
self,
provider_filter: Optional[str] = None,
model_filter: Optional[str] = None,
rotation_filter: Optional[str] = None,
autoselect_filter: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
Get model performance comparison.
Get model performance comparison with optional filters.
Args:
provider_filter: Optional provider ID to filter by
model_filter: Optional model name to filter by
rotation_filter: Optional rotation ID to filter by
autoselect_filter: Optional autoselect ID to filter by
Returns:
List of model performance data
"""
......@@ -271,6 +428,33 @@ class Analytics:
provider_id = dim['provider_id']
model_name = dim['model_name']
# Apply filters
if provider_filter and provider_id != provider_filter:
continue
if model_filter and model_name != model_filter:
continue
# Check if this is a rotation or autoselect by checking the model name
# Rotations and autoselects have special prefixes in the context dimensions
is_rotation = dim.get('is_rotation', False)
is_autoselect = dim.get('is_autoselect', False)
# Get rotation/autoselect ID from context dimensions if available
rotation_id = dim.get('rotation_id')
autoselect_id = dim.get('autoselect_id')
# Apply rotation filter
if rotation_filter:
# Skip if not a rotation or different rotation
if not is_rotation or (rotation_id and rotation_id != rotation_filter):
continue
# Apply autoselect filter
if autoselect_filter:
# Skip if not an autoselect or different autoselect
if not is_autoselect or (autoselect_id and autoselect_id != autoselect_filter):
continue
# Get token usage for this model
stats = self.db.get_token_usage_stats(provider_id, model_name)
......@@ -288,7 +472,69 @@ class Analytics:
'tokens_per_hour': stats['TPH'],
'tokens_per_day': stats['TPD'],
'error_rate': provider_stats.get('error_rate', 0),
'avg_latency_ms': provider_stats.get('avg_latency_ms', 0)
'avg_latency_ms': provider_stats.get('avg_latency_ms', 0),
'is_rotation': is_rotation,
'is_autoselect': is_autoselect,
'rotation_id': rotation_id,
'autoselect_id': autoselect_id
})
return results
def get_rotations_stats(self) -> List[Dict[str, Any]]:
"""
Get statistics for all configured rotations.
Returns:
List of rotation statistics
"""
from aisbf.config import config as cfg
results = []
for rotation_id, rotation_config in cfg.rotations.items():
# Get token usage for providers in this rotation
rotation_providers = []
for provider in rotation_config.providers:
provider_id = provider.get('provider_id')
if provider_id:
stats = self.get_provider_stats(provider_id)
rotation_providers.append(stats)
results.append({
'rotation_id': rotation_id,
'model_name': rotation_config.model_name,
'providers': rotation_providers,
'provider_count': len(rotation_providers)
})
return results
def get_autoselects_stats(self) -> List[Dict[str, Any]]:
"""
Get statistics for all configured autoselects.
Returns:
List of autoselect statistics
"""
from aisbf.config import config as cfg
results = []
for autoselect_id, autoselect_config in cfg.autoselect.items():
# Get the fallback provider info
fallback = autoselect_config.fallback
fallback_provider = None
fallback_model = None
if '/' in fallback:
fallback_provider, fallback_model = fallback.split('/', 1)
results.append({
'autoselect_id': autoselect_id,
'model_name': autoselect_config.model_name,
'description': autoselect_config.description,
'fallback_provider': fallback_provider,
'fallback_model': fallback_model,
'available_models_count': len(autoselect_config.available_models)
})
return results
......@@ -334,22 +580,44 @@ class Analytics:
completion_cost = (completion_tokens_est / 1_000_000) * provider_pricing.get('completion', 0)
return prompt_cost + completion_cost
def get_cost_overview(self) -> Dict[str, Any]:
def get_cost_overview(
self,
from_datetime: Optional[datetime] = None,
to_datetime: Optional[datetime] = None
) -> Dict[str, Any]:
"""
Get cost overview for all providers.
Args:
from_datetime: Optional start datetime for filtering
to_datetime: Optional end datetime for filtering
Returns:
Dictionary with cost estimates
"""
providers = self.get_all_providers_stats()
# Use date range for token usage if specified
start = from_datetime or (datetime.now() - timedelta(days=1))
end = to_datetime or datetime.now()
# Get token usage by date range
range_usage = self.get_token_usage_by_date_range(None, start, end)
# Get providers that have data
providers = self.get_all_providers_stats(from_datetime, to_datetime)
total_cost = 0.0
provider_costs = []
for provider in providers:
provider_id = provider['provider_id']
tokens = provider['tokens']
total_tokens = tokens['TPD'] # Use daily tokens for cost estimation
# Get token usage for this provider in the date range
if from_datetime or to_datetime:
provider_usage = self.get_token_usage_by_date_range(provider_id, start, end)
total_tokens = provider_usage['total_tokens']
else:
tokens = provider['tokens']
total_tokens = tokens['TPD'] # Use daily tokens for cost estimation
cost = self.estimate_cost(provider_id, total_tokens)
total_cost += cost
......@@ -363,7 +631,11 @@ class Analytics:
return {
'total_estimated_cost_today': total_cost,
'providers': provider_costs,
'currency': 'USD'
'currency': 'USD',
'date_range': {
'start': start.isoformat(),
'end': end.isoformat()
}
}
def get_optimization_recommendations(self) -> List[Dict[str, Any]]:
......@@ -416,12 +688,19 @@ class Analytics:
return recommendations
def export_to_json(self, time_range: str = '24h') -> str:
def export_to_json(
self,
time_range: str = '24h',
from_datetime: Optional[datetime] = None,
to_datetime: Optional[datetime] = None
) -> str:
"""
Export analytics data to JSON.
Args:
time_range: Time range for export
from_datetime: Optional custom start datetime
to_datetime: Optional custom end datetime
Returns:
JSON string with analytics data
......@@ -429,20 +708,31 @@ class Analytics:
data = {
'export_time': datetime.now().isoformat(),
'time_range': time_range,
'providers': self.get_all_providers_stats(),
'date_range': {
'from': from_datetime.isoformat() if from_datetime else None,
'to': to_datetime.isoformat() if to_datetime else None
},
'providers': self.get_all_providers_stats(from_datetime, to_datetime),
'models': self.get_model_performance(),
'cost_overview': self.get_cost_overview(),
'cost_overview': self.get_cost_overview(from_datetime, to_datetime),
'recommendations': self.get_optimization_recommendations()
}
return json.dumps(data, indent=2)
def export_to_csv(self, time_range: str = '24h') -> str:
def export_to_csv(
self,
time_range: str = '24h',
from_datetime: Optional[datetime] = None,
to_datetime: Optional[datetime] = None
) -> str:
"""
Export analytics data to CSV.
Args:
time_range: Time range for export
from_datetime: Optional custom start datetime
to_datetime: Optional custom end datetime
Returns:
CSV string with analytics data
......@@ -453,7 +743,7 @@ class Analytics:
writer = csv.writer(output)
writer.writerow(['Provider ID', 'Total Requests', 'Successful', 'Errors', 'Error Rate', 'Avg Latency (ms)', 'Tokens/Min', 'Tokens/Hour', 'Tokens/Day'])
for provider in self.get_all_providers_stats():
for provider in self.get_all_providers_stats(from_datetime, to_datetime):
writer.writerow([
provider['provider_id'],
provider['requests']['total'],
......
......@@ -22,7 +22,8 @@ Why did the programmer quit his job? Because he didn't get arrays!
Main application for AISBF.
"""
from fastapi import FastAPI, HTTPException, Request, status, Form
from typing import Optional
from fastapi import FastAPI, HTTPException, Request, status, Form, Query
from fastapi.responses import JSONResponse, StreamingResponse, HTMLResponse, RedirectResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.exceptions import RequestValidationError
......@@ -1102,7 +1103,16 @@ app.add_middleware(
# Dashboard routes
@app.get("/dashboard/analytics", response_class=HTMLResponse)
async def dashboard_analytics(request: Request):
async def dashboard_analytics(
request: Request,
time_range: str = Query("24h"),
from_date: Optional[str] = Query(None),
to_date: Optional[str] = Query(None),
provider_filter: Optional[str] = Query(None),
model_filter: Optional[str] = Query(None),
rotation_filter: Optional[str] = Query(None),
autoselect_filter: Optional[str] = Query(None)
):
"""Token usage analytics dashboard"""
auth_check = require_dashboard_auth(request)
if auth_check:
......@@ -1115,21 +1125,74 @@ async def dashboard_analytics(request: Request):
db = get_database()
analytics = get_analytics(db)
# Get provider statistics
provider_stats = analytics.get_all_providers_stats()
# Parse date range
from_datetime = None
to_datetime = None
if from_date:
try:
from_datetime = datetime.fromisoformat(from_date.replace('Z', '+00:00'))
except ValueError:
pass
if to_date:
try:
to_datetime = datetime.fromisoformat(to_date.replace('Z', '+00:00'))
except ValueError:
pass
# Get token usage over time
token_over_time = analytics.get_token_usage_over_time(time_range='24h')
# If custom date range is provided, use custom mode
if from_datetime and to_datetime:
time_range = 'custom'
# Get model performance
model_performance = analytics.get_model_performance()
# Get available providers, models, rotations, and autoselects for filter dropdowns
available_providers = list(config.providers.keys()) if config else []
available_rotations = list(config.rotations.keys()) if config else []
available_autoselects = list(config.autoselect.keys()) if config else []
# Get models from providers
available_models = []
if config and hasattr(config, 'providers'):
for provider_id, provider_config in config.providers.items():
if hasattr(provider_config, 'models') and provider_config.models:
for model in provider_config.models:
available_models.append(f"{provider_id}/{model.name}")
# Get provider statistics (with optional filter)
if provider_filter:
provider_stats = [analytics.get_provider_stats(provider_filter, from_datetime, to_datetime)]
else:
provider_stats = analytics.get_all_providers_stats(from_datetime, to_datetime)
# Get token usage over time (with optional filters)
token_over_time = analytics.get_token_usage_over_time(
provider_id=provider_filter,
time_range=time_range,
from_datetime=from_datetime,
to_datetime=to_datetime
)
# Get model performance (with optional filters)
model_performance = analytics.get_model_performance(
provider_filter=provider_filter,
model_filter=model_filter,
rotation_filter=rotation_filter,
autoselect_filter=autoselect_filter
)
# Get cost overview
cost_overview = analytics.get_cost_overview()
cost_overview = analytics.get_cost_overview(from_datetime, to_datetime)
# Get optimization recommendations
recommendations = analytics.get_optimization_recommendations()
# Get date range usage summary
date_range_usage = None
if from_datetime or to_datetime:
start = from_datetime or (datetime.now() - timedelta(days=1))
end = to_datetime or datetime.now()
date_range_usage = analytics.get_token_usage_by_date_range(provider_filter, start, end)
return templates.TemplateResponse("dashboard/analytics.html", {
"request": request,
"session": request.session,
......@@ -1137,7 +1200,19 @@ async def dashboard_analytics(request: Request):
"token_over_time": json.dumps(token_over_time),
"model_performance": model_performance,
"cost_overview": cost_overview,
"recommendations": recommendations
"recommendations": recommendations,
"selected_time_range": time_range,
"from_date": from_date,
"to_date": to_date,
"date_range_usage": date_range_usage,
"available_providers": available_providers,
"available_models": available_models,
"available_rotations": available_rotations,
"available_autoselects": available_autoselects,
"selected_provider": provider_filter,
"selected_model": model_filter,
"selected_rotation": rotation_filter,
"selected_autoselect": autoselect_filter
})
@app.get("/dashboard/login", response_class=HTMLResponse)
......
......@@ -21,6 +21,148 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
{% block content %}
<h2 style="margin-bottom: 30px;">Token Usage Analytics</h2>
<!-- Date Range Filter Section -->
<div style="background: #1a1a2e; padding: 20px; border-radius: 8px; margin-bottom: 30px;">
<h3 style="margin-bottom: 15px;">Filter by Date Range</h3>
<form method="get" action="/dashboard/analytics" style="display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-end;">
<div style="flex: 1; min-width: 200px;">
<label style="display: block; margin-bottom: 5px; color: #a0a0a0; font-size: 14px;">Time Range</label>
<select name="time_range" id="timeRangeSelect" style="width: 100%; padding: 10px; border-radius: 4px; background: #0f3460; color: white; border: 1px solid #2a4a7a;">
<option value="1h" {% if selected_time_range == '1h' %}selected{% endif %}>Last 1 Hour</option>
<option value="6h" {% if selected_time_range == '6h' %}selected{% endif %}>Last 6 Hours</option>
<option value="24h" {% if selected_time_range == '24h' %}selected{% endif %}>Last 24 Hours</option>
<option value="7d" {% if selected_time_range == '7d' %}selected{% endif %}>Last 7 Days</option>
<option value="30d" {% if selected_time_range == '30d' %}selected{% endif %}>Last 30 Days</option>
<option value="90d" {% if selected_time_range == '90d' %}selected{% endif %}>Last 90 Days</option>
<option value="custom" {% if selected_time_range == 'custom' %}selected{% endif %}>Custom Range</option>
</select>
</div>
<div id="customDateRange" style="display: {% if selected_time_range == 'custom' or from_date %}flex{% else %}none{% endif %}; flex-wrap: wrap; gap: 15px; flex: 2;">
<div style="flex: 1; min-width: 200px;">
<label style="display: block; margin-bottom: 5px; color: #a0a0a0; font-size: 14px;">From Date & Time</label>
<input type="datetime-local" name="from_date" value="{{ from_date or '' }}"
style="width: 100%; padding: 10px; border-radius: 4px; background: #0f3460; color: white; border: 1px solid #2a4a7a;">
</div>
<div style="flex: 1; min-width: 200px;">
<label style="display: block; margin-bottom: 5px; color: #a0a0a0; font-size: 14px;">To Date & Time</label>
<input type="datetime-local" name="to_date" value="{{ to_date or '' }}"
style="width: 100%; padding: 10px; border-radius: 4px; background: #0f3460; color: white; border: 1px solid #2a4a7a;">
</div>
</div>
<div>
<button type="submit" style="padding: 10px 20px; background: #e94560; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
Apply Filter
</button>
</div>
</form>
{% if from_date or to_date %}
<div style="margin-top: 15px; padding: 10px; background: #0f3460; border-radius: 4px;">
<strong style="color: #60a5fa;">Selected Range: </strong>
<span style="color: #e0e0e0;">
{% if from_date %}{{ from_date }}{% else %}Beginning{% endif %}
to
{% if to_date %}{{ to_date }}{% else %}Now{% endif %}
</span>
{% if date_range_usage %}
<span style="margin-left: 20px; color: #a0a0a0;">
| Total: {{ date_range_usage.total_tokens }} tokens | Estimated Cost: ${{ "%.2f"|format(date_range_usage.estimated_cost) }}
</span>
{% endif %}
</div>
{% endif %}
</div>
<!-- Filter by Provider/Model/Rotation/Autoselect -->
<div style="background: #1a1a2e; padding: 20px; border-radius: 8px; margin-bottom: 30px;">
<h3 style="margin-bottom: 15px;">Filter by Provider, Model, Rotation, or Autoselect</h3>
<form method="get" action="/dashboard/analytics" style="display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-end;">
<!-- Preserve date filter parameters -->
<input type="hidden" name="time_range" value="{{ selected_time_range }}">
{% if from_date %}<input type="hidden" name="from_date" value="{{ from_date }}">{% endif %}
{% if to_date %}<input type="hidden" name="to_date" value="{{ to_date }}">{% endif %}
<div style="flex: 1; min-width: 150px;">
<label style="display: block; margin-bottom: 5px; color: #a0a0a0; font-size: 14px;">Provider</label>
<select name="provider_filter" style="width: 100%; padding: 10px; border-radius: 4px; background: #0f3460; color: white; border: 1px solid #2a4a7a;">
<option value="">All Providers</option>
{% for provider in available_providers %}
<option value="{{ provider }}" {% if selected_provider == provider %}selected{% endif %}>{{ provider }}</option>
{% endfor %}
</select>
</div>
<div style="flex: 1; min-width: 150px;">
<label style="display: block; margin-bottom: 5px; color: #a0a0a0; font-size: 14px;">Model</label>
<select name="model_filter" style="width: 100%; padding: 10px; border-radius: 4px; background: #0f3460; color: white; border: 1px solid #2a4a7a;">
<option value="">All Models</option>
{% for model in available_models %}
<option value="{{ model }}" {% if selected_model == model %}selected{% endif %}>{{ model }}</option>
{% endfor %}
</select>
</div>
<div style="flex: 1; min-width: 150px;">
<label style="display: block; margin-bottom: 5px; color: #a0a0a0; font-size: 14px;">Rotation</label>
<select name="rotation_filter" style="width: 100%; padding: 10px; border-radius: 4px; background: #0f3460; color: white; border: 1px solid #2a4a7a;">
<option value="">All Rotations</option>
{% for rotation in available_rotations %}
<option value="{{ rotation }}" {% if selected_rotation == rotation %}selected{% endif %}>{{ rotation }}</option>
{% endfor %}
</select>
</div>
<div style="flex: 1; min-width: 150px;">
<label style="display: block; margin-bottom: 5px; color: #a0a0a0; font-size: 14px;">Autoselect</label>
<select name="autoselect_filter" style="width: 100%; padding: 10px; border-radius: 4px; background: #0f3460; color: white; border: 1px solid #2a4a7a;">
<option value="">All Autoselects</option>
{% for autoselect in available_autoselects %}
<option value="{{ autoselect }}" {% if selected_autoselect == autoselect %}selected{% endif %}>{{ autoselect }}</option>
{% endfor %}
</select>
</div>
<div>
<button type="submit" style="padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
Apply Filters
</button>
</div>
{% if selected_provider or selected_model or selected_rotation or selected_autoselect %}
<div>
<a href="/dashboard/analytics?time_range={{ selected_time_range }}{% if from_date %}&from_date={{ from_date }}{% endif %}{% if to_date %}&to_date={{ to_date }}{% endif %}" style="padding: 10px 20px; background: #7f8c8d; color: white; border: none; border-radius: 4px; text-decoration: none; display: inline-block;">
Clear Filters
</a>
</div>
{% endif %}
</form>
{% if selected_provider or selected_model or selected_rotation or selected_autoselect %}
<div style="margin-top: 15px; padding: 10px; background: #0f3460; border-radius: 4px;">
<strong style="color: #60a5fa;">Active Filters: </strong>
<span style="color: #e0e0e0;">
{% if selected_provider %}Provider: {{ selected_provider }}{% endif %}
{% if selected_model %}{% if selected_provider %} | {% endif %}Model: {{ selected_model }}{% endif %}
{% if selected_rotation %}{% if selected_provider or selected_model %} | {% endif %}Rotation: {{ selected_rotation }}{% endif %}
{% if selected_autoselect %}{% if selected_provider or selected_model or selected_rotation %} | {% endif %}Autoselect: {{ selected_autoselect }}{% endif %}
</span>
</div>
{% endif %}
</div>
<script>
document.getElementById('timeRangeSelect').addEventListener('change', function() {
var customRange = document.getElementById('customDateRange');
if (this.value === 'custom') {
customRange.style.display = 'flex';
} else {
customRange.style.display = 'none';
}
});
</script>
{% if recommendations %}
<h3 style="margin-bottom: 15px;">Optimization Recommendations</h3>
<div style="margin-bottom: 30px;">
......@@ -77,14 +219,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<h3 style="margin-top: 30px; margin-bottom: 15px;">Cost Overview</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px;">
<div style="background: #2ecc71; color: white; padding: 20px; border-radius: 8px;">
<h4 style="font-size: 14px; margin-bottom: 10px;">Today's Estimated Cost</h4>
<h4 style="font-size: 14px; margin-bottom: 10px;">
{% if from_date or to_date %}Selected Period Cost{% else %}Today's Estimated Cost{% endif %}
</h4>
<p style="font-size: 28px; font-weight: bold;">${{ "%.2f"|format(cost_overview.total_estimated_cost_today) }}</p>
{% if cost_overview.date_range %}
<small style="color: rgba(255,255,255,0.8);">
{{ cost_overview.date_range.start[:10] }} to {{ cost_overview.date_range.end[:10] }}
</small>
{% endif %}
</div>
{% for pc in cost_overview.providers %}
<div style="background: #0f3460; padding: 15px; border-radius: 8px;">
<h4 style="font-size: 14px; margin-bottom: 5px;">{{ pc.provider_id }}</h4>
<p style="font-size: 20px; font-weight: bold;">${{ "%.2f"|format(pc.estimated_cost) }}</p>
<small style="color: #a0a0a0;">{{ pc.tokens_today }} tokens today</small>
<small style="color: #a0a0a0;">{{ pc.tokens_today }} tokens</small>
</div>
{% endfor %}
</div>
......@@ -95,6 +244,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<tr>
<th>Provider</th>
<th>Model</th>
<th>Type</th>
<th>Context Size</th>
<th>Condense %</th>
<th>Condense Method</th>
......@@ -106,6 +256,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<tr>
<td>{{ model.provider_id }}</td>
<td>{{ model.model_name }}</td>
<td>
{% if model.is_rotation %}
<span style="color: #f39c12;">Rotation: {{ model.rotation_id }}</span>
{% elif model.is_autoselect %}
<span style="color: #9b59b6;">Autoselect: {{ model.autoselect_id }}</span>
{% else %}
<span style="color: #3498db;">Provider Model</span>
{% endif %}
</td>
<td>{{ model.context_size|default('N/A') }}</td>
<td>{{ model.condense_context|default('N/A') }}%</td>
<td>{{ model.condense_method|default('None') }}</td>
......@@ -123,12 +282,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<p style="color: #a0a0a0;">No model performance data available yet.</p>
{% endif %}
<h3 style="margin-top: 30px; margin-bottom: 15px;">Token Usage Over Time (24h)</h3>
<h3 style="margin-top: 30px; margin-bottom: 15px;">Token Usage Over Time {% if from_date or to_date %}(Custom Range){% else %}(24h){% endif %}</h3>
{% if token_over_time != '[]' %}
<div style="background: #1a1a2e; padding: 20px; border-radius: 8px;">
<canvas id="tokenChart" style="width: 100%; height: 300px;"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
<script>
const tokenData = {{ token_over_time|safe }};
......@@ -159,7 +319,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
options: {
responsive: true,
scales: {
x: { type: 'time', time: { unit: 'hour' } },
x: {
type: 'time',
time: {
unit: {% if selected_time_range == '1h' or selected_time_range == '6h' %}'hour'{% elif selected_time_range == '24h' %}'hour'{% elif selected_time_range == '7d' %}'day'{% else %}'day'{% endif %}
},
title: { display: true, text: 'Time' }
},
y: { beginAtZero: true, title: { display: true, text: 'Tokens' } }
}
}
......@@ -169,10 +335,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
new Chart(document.getElementById('tokenChart'), {
type: 'line',
data: {
labels: tokenData.map(d => d.timestamp),
datasets: [{
label: 'Tokens Used',
data: tokenData.map(d => d.tokens),
data: tokenData.map(d => ({x: d.timestamp, y: d.tokens})),
borderColor: '#e94560',
backgroundColor: 'rgba(233, 69, 96, 0.1)',
fill: true,
......@@ -182,7 +347,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
options: {
responsive: true,
scales: {
x: { title: { display: true, text: 'Time' } },
x: {
type: 'time',
time: {
unit: {% if selected_time_range == '1h' or selected_time_range == '6h' %}'hour'{% elif selected_time_range == '24h' %}'hour'{% elif selected_time_range == '7d' %}'day'{% else %}'day'{% endif %}
},
title: { display: true, text: 'Time' }
},
y: { beginAtZero: true, title: { display: true, text: 'Tokens' } }
}
}
......@@ -190,7 +361,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
}
</script>
{% else %}
<p style="color: #a0a0a0;">No token usage data available yet.</p>
<p style="color: #a0a0a0;">No token usage data available for the selected period.</p>
{% endif %}
<div style="margin-top: 30px; display: flex; gap: 10px;">
......
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