| | 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""" |
| | |
| | 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(), |
| | } |
| | |
| | |
| | recent_votes = Vote.query.order_by(Vote.vote_date.desc()).limit(10).all() |
| | |
| | |
| | recent_users = User.query.order_by(User.join_date.desc()).limit(10).all() |
| | |
| | |
| | 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() |
| | |
| | |
| | date_list = [] |
| | current_date = datetime.utcnow() |
| | for i in range(30, -1, -1): |
| | date_list.append((current_date - timedelta(days=i)).date()) |
| | |
| | |
| | vote_counts = {day.date: day.count for day in daily_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 |
| | } |
| | |
| | |
| | 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) |
| | |
| | |
| | recent_votes = Vote.query.filter_by(user_id=user_id).order_by(Vote.vote_date.desc()).limit(20).all() |
| | |
| | |
| | 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() |
| | |
| | |
| | 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 |
| | |
| | |
| | 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""" |
| | |
| | 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_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() |
| | |
| | |
| | date_list = [] |
| | current_date = datetime.utcnow() |
| | for i in range(30, -1, -1): |
| | date_list.append((current_date - timedelta(days=i)).date()) |
| | |
| | |
| | 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} |
| | |
| | |
| | formatted_dates = [date.strftime("%Y-%m-%d") for date in date_list] |
| | |
| | |
| | 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] |
| | |
| | |
| | current_date = datetime.utcnow() |
| | month_list = [] |
| | for i in range(11, -1, -1): |
| | past_date = current_date - timedelta(days=i*30) |
| | month_list.append((past_date.year, past_date.month)) |
| | |
| | |
| | user_counts = {(record.year, record.month): record.count for record in monthly_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] |
| | |
| | |
| | top_models = Model.query.order_by(Model.match_count.desc()).limit(5).all() |
| | |
| | |
| | earliest = datetime.utcnow() - timedelta(days=30) |
| | latest = datetime.utcnow() |
| | |
| | |
| | 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 not has_elo_history: |
| | earliest = datetime.utcnow() - timedelta(days=30) |
| | latest = datetime.utcnow() |
| | |
| | |
| | if earliest > latest: |
| | earliest = latest - timedelta(days=30) |
| | |
| | |
| | |
| | elo_dates = [] |
| | current = earliest |
| | while current <= latest: |
| | elo_dates.append(current.date()) |
| | current += timedelta(days=1) |
| | |
| | |
| | formatted_elo_dates = [date.strftime("%Y-%m-%d") for date in elo_dates] |
| | |
| | model_history = {} |
| | |
| | |
| | for model in top_models: |
| | model_history[model.name] = { |
| | "timestamps": formatted_elo_dates, |
| | "scores": [None] * len(formatted_elo_dates) |
| | } |
| | |
| | history = EloHistory.query.filter_by( |
| | model_id=model.id |
| | ).order_by(EloHistory.timestamp).all() |
| | |
| | if history: |
| | |
| | history_dict = {} |
| | for h in history: |
| | date_key = h.timestamp.date().strftime("%Y-%m-%d") |
| | history_dict[date_key] = h.elo_score |
| | |
| | |
| | last_score = model.current_elo |
| | 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: |
| | |
| | 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""" |
| | |
| | tts_session_count = 0 |
| | conversational_session_count = 0 |
| | |
| | |
| | if hasattr(current_app, 'tts_sessions'): |
| | tts_session_count = len(current_app.tts_sessions) |
| | else: |
| | 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: |
| | from app import conversational_sessions |
| | conversational_session_count = len(conversational_sessions) |
| | |
| | |
| | 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() |
| | |
| | |
| | current_time = datetime.utcnow() |
| | last_24h = current_time.replace(minute=0, second=0, microsecond=0) - timedelta(hours=24) |
| | |
| | |
| | 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() |
| | |
| | |
| | hour_list = [] |
| | for i in range(24, -1, -1): |
| | |
| | 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')) |
| | |
| | |
| | vote_counts = {hour.hour: hour.count for hour in hourly_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) |
| | ) |