TTS-Arena-V2 / admin.py
GitHub Actions
Sync from GitHub repo
f1a0148
raw
history blame contribute delete
14.6 kB
from flask import Blueprint, render_template, current_app, jsonify, request, redirect, url_for, flash
from models import db, User, Model, Vote, EloHistory, ModelType
from auth import admin_required
from sqlalchemy import func, desc, extract
from datetime import datetime, timedelta
import json
import os
admin = Blueprint("admin", __name__, url_prefix="/admin")
@admin.route("/")
@admin_required
def index():
"""Admin dashboard homepage"""
# Get count statistics
stats = {
"total_users": User.query.count(),
"total_votes": Vote.query.count(),
"tts_votes": Vote.query.filter_by(model_type=ModelType.TTS).count(),
"conversational_votes": Vote.query.filter_by(model_type=ModelType.CONVERSATIONAL).count(),
"tts_models": Model.query.filter_by(model_type=ModelType.TTS).count(),
"conversational_models": Model.query.filter_by(model_type=ModelType.CONVERSATIONAL).count(),
}
# Get recent votes
recent_votes = Vote.query.order_by(Vote.vote_date.desc()).limit(10).all()
# Get recent users
recent_users = User.query.order_by(User.join_date.desc()).limit(10).all()
# Get daily votes for the past 30 days
thirty_days_ago = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=30)
daily_votes = db.session.query(
func.date(Vote.vote_date).label('date'),
func.count().label('count')
).filter(Vote.vote_date >= thirty_days_ago).group_by(
func.date(Vote.vote_date)
).order_by(func.date(Vote.vote_date)).all()
# Generate a complete list of dates for the past 30 days
date_list = []
current_date = datetime.utcnow()
for i in range(30, -1, -1):
date_list.append((current_date - timedelta(days=i)).date())
# Create a dictionary with actual vote counts
vote_counts = {day.date: day.count for day in daily_votes}
# Build complete datasets including days with zero votes
formatted_dates = [date.strftime("%Y-%m-%d") for date in date_list]
vote_counts_list = [vote_counts.get(date, 0) for date in date_list]
daily_votes_data = {
"labels": formatted_dates,
"counts": vote_counts_list
}
# Get top models
top_tts_models = Model.query.filter_by(
model_type=ModelType.TTS
).order_by(Model.current_elo.desc()).limit(5).all()
top_conversational_models = Model.query.filter_by(
model_type=ModelType.CONVERSATIONAL
).order_by(Model.current_elo.desc()).limit(5).all()
return render_template(
"admin/index.html",
stats=stats,
recent_votes=recent_votes,
recent_users=recent_users,
daily_votes_data=json.dumps(daily_votes_data),
top_tts_models=top_tts_models,
top_conversational_models=top_conversational_models
)
@admin.route("/models")
@admin_required
def models():
"""Manage models"""
tts_models = Model.query.filter_by(model_type=ModelType.TTS).order_by(Model.name).all()
conversational_models = Model.query.filter_by(model_type=ModelType.CONVERSATIONAL).order_by(Model.name).all()
return render_template(
"admin/models.html",
tts_models=tts_models,
conversational_models=conversational_models
)
@admin.route("/model/<model_id>", methods=["GET", "POST"])
@admin_required
def edit_model(model_id):
"""Edit a model"""
model = Model.query.get_or_404(model_id)
if request.method == "POST":
model.name = request.form.get("name")
model.is_active = "is_active" in request.form
model.is_open = "is_open" in request.form
model.model_url = request.form.get("model_url")
db.session.commit()
flash(f"Model '{model.name}' updated successfully", "success")
return redirect(url_for("admin.models"))
return render_template("admin/edit_model.html", model=model)
@admin.route("/users")
@admin_required
def users():
"""Manage users"""
users = User.query.order_by(User.username).all()
admin_users = os.getenv("ADMIN_USERS", "").split(",")
admin_users = [username.strip() for username in admin_users]
return render_template("admin/users.html", users=users, admin_users=admin_users)
@admin.route("/user/<int:user_id>")
@admin_required
def user_detail(user_id):
"""View user details"""
user = User.query.get_or_404(user_id)
# Get user votes
recent_votes = Vote.query.filter_by(user_id=user_id).order_by(Vote.vote_date.desc()).limit(20).all()
# Get vote statistics
tts_votes = Vote.query.filter_by(user_id=user_id, model_type=ModelType.TTS).count()
conversational_votes = Vote.query.filter_by(user_id=user_id, model_type=ModelType.CONVERSATIONAL).count()
# Get favorite models (most chosen)
favorite_models = db.session.query(
Vote.model_chosen,
Model.name,
func.count().label('count')
).join(
Model, Vote.model_chosen == Model.id
).filter(
Vote.user_id == user_id
).group_by(
Vote.model_chosen, Model.name
).order_by(
desc('count')
).limit(5).all()
return render_template(
"admin/user_detail.html",
user=user,
recent_votes=recent_votes,
tts_votes=tts_votes,
conversational_votes=conversational_votes,
favorite_models=favorite_models,
total_votes=tts_votes + conversational_votes
)
@admin.route("/votes")
@admin_required
def votes():
"""View recent votes"""
page = request.args.get('page', 1, type=int)
per_page = 50
# Get votes with pagination
votes_pagination = Vote.query.order_by(
Vote.vote_date.desc()
).paginate(page=page, per_page=per_page)
return render_template(
"admin/votes.html",
votes=votes_pagination.items,
pagination=votes_pagination
)
@admin.route("/statistics")
@admin_required
def statistics():
"""View detailed statistics"""
# Get daily votes for the past 30 days by model type
thirty_days_ago = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=30)
tts_daily_votes = db.session.query(
func.date(Vote.vote_date).label('date'),
func.count().label('count')
).filter(
Vote.vote_date >= thirty_days_ago,
Vote.model_type == ModelType.TTS
).group_by(
func.date(Vote.vote_date)
).order_by(func.date(Vote.vote_date)).all()
conv_daily_votes = db.session.query(
func.date(Vote.vote_date).label('date'),
func.count().label('count')
).filter(
Vote.vote_date >= thirty_days_ago,
Vote.model_type == ModelType.CONVERSATIONAL
).group_by(
func.date(Vote.vote_date)
).order_by(func.date(Vote.vote_date)).all()
# Monthly new users
monthly_users = db.session.query(
extract('year', User.join_date).label('year'),
extract('month', User.join_date).label('month'),
func.count().label('count')
).group_by(
'year', 'month'
).order_by('year', 'month').all()
# Generate a complete list of dates for the past 30 days
date_list = []
current_date = datetime.utcnow()
for i in range(30, -1, -1):
date_list.append((current_date - timedelta(days=i)).date())
# Create dictionaries with actual vote counts
tts_vote_counts = {day.date: day.count for day in tts_daily_votes}
conv_vote_counts = {day.date: day.count for day in conv_daily_votes}
# Format dates consistently for charts
formatted_dates = [date.strftime("%Y-%m-%d") for date in date_list]
# Build complete datasets including days with zero votes
tts_counts = [tts_vote_counts.get(date, 0) for date in date_list]
conv_counts = [conv_vote_counts.get(date, 0) for date in date_list]
# Generate all month/year combinations for the past 12 months
current_date = datetime.utcnow()
month_list = []
for i in range(11, -1, -1):
past_date = current_date - timedelta(days=i*30) # Approximate
month_list.append((past_date.year, past_date.month))
# Create a dictionary with actual user counts
user_counts = {(record.year, record.month): record.count for record in monthly_users}
# Build complete monthly datasets including months with zero new users
monthly_labels = [f"{month}/{year}" for year, month in month_list]
monthly_counts = [user_counts.get((year, month), 0) for year, month in month_list]
# Model performance over time
top_models = Model.query.order_by(Model.match_count.desc()).limit(5).all()
# Get first and last timestamp to create a consistent timeline
earliest = datetime.utcnow() - timedelta(days=30) # Default to 30 days ago
latest = datetime.utcnow() # Default to now
# Find actual earliest and latest timestamps across all models
has_elo_history = False
for model in top_models:
first = EloHistory.query.filter_by(model_id=model.id).order_by(EloHistory.timestamp).first()
last = EloHistory.query.filter_by(model_id=model.id).order_by(EloHistory.timestamp.desc()).first()
if first and last:
has_elo_history = True
if first.timestamp < earliest:
earliest = first.timestamp
if last.timestamp > latest:
latest = last.timestamp
# If no history was found, use a default range of the last 30 days
if not has_elo_history:
earliest = datetime.utcnow() - timedelta(days=30)
latest = datetime.utcnow()
# Make sure the date range is valid (earliest before latest)
if earliest > latest:
earliest = latest - timedelta(days=30)
# Generate a list of dates for the ELO history timeline
# Using 1-day intervals for a smoother chart
elo_dates = []
current = earliest
while current <= latest:
elo_dates.append(current.date())
current += timedelta(days=1)
# Format dates consistently
formatted_elo_dates = [date.strftime("%Y-%m-%d") for date in elo_dates]
model_history = {}
# Initialize empty data for all top models
for model in top_models:
model_history[model.name] = {
"timestamps": formatted_elo_dates,
"scores": [None] * len(formatted_elo_dates) # Initialize with None values
}
history = EloHistory.query.filter_by(
model_id=model.id
).order_by(EloHistory.timestamp).all()
if history:
# Create a dictionary mapping dates to scores
history_dict = {}
for h in history:
date_key = h.timestamp.date().strftime("%Y-%m-%d")
history_dict[date_key] = h.elo_score
# Fill in missing dates with the previous score
last_score = model.current_elo # Default to current ELO if no history
scores = []
for date in formatted_elo_dates:
if date in history_dict:
last_score = history_dict[date]
scores.append(last_score)
model_history[model.name]["scores"] = scores
else:
# If no history, use the current Elo for all dates
model_history[model.name]["scores"] = [model.current_elo] * len(formatted_elo_dates)
chart_data = {
"dailyVotes": {
"labels": formatted_dates,
"ttsCounts": tts_counts,
"convCounts": conv_counts
},
"monthlyUsers": {
"labels": monthly_labels,
"counts": monthly_counts
},
"modelHistory": model_history
}
return render_template(
"admin/statistics.html",
chart_data=json.dumps(chart_data)
)
@admin.route("/activity")
@admin_required
def activity():
"""View recent text generations"""
# Check if we have any active sessions from app.py
tts_session_count = 0
conversational_session_count = 0
# Access global variables from app.py through current_app
if hasattr(current_app, 'tts_sessions'):
tts_session_count = len(current_app.tts_sessions)
else: # Try to access through app module
from app import tts_sessions
tts_session_count = len(tts_sessions)
if hasattr(current_app, 'conversational_sessions'):
conversational_session_count = len(current_app.conversational_sessions)
else: # Try to access through app module
from app import conversational_sessions
conversational_session_count = len(conversational_sessions)
# Get recent votes which represent completed generations
recent_tts_votes = Vote.query.filter_by(
model_type=ModelType.TTS
).order_by(Vote.vote_date.desc()).limit(20).all()
recent_conv_votes = Vote.query.filter_by(
model_type=ModelType.CONVERSATIONAL
).order_by(Vote.vote_date.desc()).limit(20).all()
# Get votes per hour for the last 24 hours
current_time = datetime.utcnow()
last_24h = current_time.replace(minute=0, second=0, microsecond=0) - timedelta(hours=24)
# Use SQLite-compatible date formatting
hourly_votes = db.session.query(
func.strftime('%Y-%m-%d %H:00', Vote.vote_date).label('hour'),
func.count().label('count')
).filter(
Vote.vote_date >= last_24h
).group_by('hour').order_by('hour').all()
# Generate all hours for the past 24 hours with correct hour formatting
hour_list = []
for i in range(24, -1, -1):
# Calculate the hour time and truncate to hour
hour_time = current_time - timedelta(hours=i)
hour_time = hour_time.replace(minute=0, second=0, microsecond=0)
hour_list.append(hour_time.strftime('%Y-%m-%d %H:00'))
# Create a dictionary with actual vote counts
vote_counts = {hour.hour: hour.count for hour in hourly_votes}
# Build complete hourly datasets including hours with zero votes
hourly_data = {
"labels": hour_list,
"counts": [vote_counts.get(hour, 0) for hour in hour_list]
}
return render_template(
"admin/activity.html",
tts_session_count=tts_session_count,
conversational_session_count=conversational_session_count,
recent_tts_votes=recent_tts_votes,
recent_conv_votes=recent_conv_votes,
hourly_data=json.dumps(hourly_data)
)