Spaces:
Sleeping
Sleeping
Ko-TTS-Arena Contributors
commited on
Commit
·
88cf8f6
1
Parent(s):
f49c679
stats
Browse files- app.py +2 -2
- models.py +37 -0
- templates/leaderboard.html +203 -56
app.py
CHANGED
|
@@ -375,7 +375,7 @@ def arena():
|
|
| 375 |
@app.route("/leaderboard")
|
| 376 |
def leaderboard():
|
| 377 |
tts_leaderboard = get_leaderboard_data(ModelType.TTS)
|
| 378 |
-
|
| 379 |
|
| 380 |
# Initialize personal leaderboard data
|
| 381 |
tts_personal_leaderboard = None
|
|
@@ -398,7 +398,7 @@ def leaderboard():
|
|
| 398 |
tts_personal_leaderboard=tts_personal_leaderboard,
|
| 399 |
tts_key_dates=tts_key_dates,
|
| 400 |
formatted_tts_dates=formatted_tts_dates,
|
| 401 |
-
|
| 402 |
user_leaderboard_visibility=user_leaderboard_visibility
|
| 403 |
)
|
| 404 |
|
|
|
|
| 375 |
@app.route("/leaderboard")
|
| 376 |
def leaderboard():
|
| 377 |
tts_leaderboard = get_leaderboard_data(ModelType.TTS)
|
| 378 |
+
voting_stats = get_voting_statistics() # Get voting statistics
|
| 379 |
|
| 380 |
# Initialize personal leaderboard data
|
| 381 |
tts_personal_leaderboard = None
|
|
|
|
| 398 |
tts_personal_leaderboard=tts_personal_leaderboard,
|
| 399 |
tts_key_dates=tts_key_dates,
|
| 400 |
formatted_tts_dates=formatted_tts_dates,
|
| 401 |
+
voting_stats=voting_stats,
|
| 402 |
user_leaderboard_visibility=user_leaderboard_visibility
|
| 403 |
)
|
| 404 |
|
models.py
CHANGED
|
@@ -688,6 +688,43 @@ def get_top_voters(limit=10):
|
|
| 688 |
return result
|
| 689 |
|
| 690 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 691 |
def toggle_user_leaderboard_visibility(user_id):
|
| 692 |
"""Toggle user's leaderboard visibility setting"""
|
| 693 |
user = User.query.get(user_id)
|
|
|
|
| 688 |
return result
|
| 689 |
|
| 690 |
|
| 691 |
+
def get_voting_statistics():
|
| 692 |
+
"""
|
| 693 |
+
Get voting statistics for the arena.
|
| 694 |
+
|
| 695 |
+
Returns:
|
| 696 |
+
dict: Dictionary containing voting statistics
|
| 697 |
+
"""
|
| 698 |
+
# Total number of unique voters
|
| 699 |
+
total_voters = db.session.query(func.count(func.distinct(Vote.user_id))).scalar() or 0
|
| 700 |
+
|
| 701 |
+
# Total number of votes
|
| 702 |
+
total_votes = Vote.query.count()
|
| 703 |
+
|
| 704 |
+
# Average votes per user
|
| 705 |
+
avg_votes_per_user = round(total_votes / total_voters, 1) if total_voters > 0 else 0
|
| 706 |
+
|
| 707 |
+
# Votes in the last 24 hours
|
| 708 |
+
yesterday = datetime.utcnow() - timedelta(days=1)
|
| 709 |
+
votes_last_24h = Vote.query.filter(Vote.vote_date >= yesterday).count()
|
| 710 |
+
|
| 711 |
+
# Votes in the last 7 days
|
| 712 |
+
last_week = datetime.utcnow() - timedelta(days=7)
|
| 713 |
+
votes_last_7d = Vote.query.filter(Vote.vote_date >= last_week).count()
|
| 714 |
+
|
| 715 |
+
# Total number of active models
|
| 716 |
+
active_models = Model.query.filter_by(model_type=ModelType.TTS, is_active=True).count()
|
| 717 |
+
|
| 718 |
+
return {
|
| 719 |
+
"total_voters": total_voters,
|
| 720 |
+
"total_votes": total_votes,
|
| 721 |
+
"avg_votes_per_user": avg_votes_per_user,
|
| 722 |
+
"votes_last_24h": votes_last_24h,
|
| 723 |
+
"votes_last_7d": votes_last_7d,
|
| 724 |
+
"active_models": active_models,
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
|
| 728 |
def toggle_user_leaderboard_visibility(user_id):
|
| 729 |
"""Toggle user's leaderboard visibility setting"""
|
| 730 |
user = User.query.get(user_id)
|
templates/leaderboard.html
CHANGED
|
@@ -709,6 +709,156 @@
|
|
| 709 |
color: var(--text-color);
|
| 710 |
}
|
| 711 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
.disputed-badge {
|
| 713 |
display: inline-flex;
|
| 714 |
align-items: center;
|
|
@@ -765,7 +915,7 @@
|
|
| 765 |
{% block content %}
|
| 766 |
<div class="tabs">
|
| 767 |
<div class="tab active" data-tab="tts">TTS</div>
|
| 768 |
-
<div class="tab" data-tab="
|
| 769 |
</div>
|
| 770 |
|
| 771 |
<div id="tts-tab" class="tab-content">
|
|
@@ -952,65 +1102,62 @@
|
|
| 952 |
</div>
|
| 953 |
</div>
|
| 954 |
|
| 955 |
-
<!--
|
| 956 |
-
<div id="
|
| 957 |
-
<div class="
|
| 958 |
-
<
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
<div class="
|
| 962 |
-
<
|
| 963 |
-
<
|
| 964 |
-
<
|
| 965 |
-
<
|
| 966 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 967 |
</div>
|
| 968 |
-
{% endif %}
|
| 969 |
</div>
|
| 970 |
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
<
|
| 974 |
-
<
|
| 975 |
-
<
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
</
|
| 982 |
-
|
| 983 |
-
{% for voter in top_voters %}
|
| 984 |
-
<tr{% if current_user.is_authenticated and current_user.username == voter.username %} class="current-user"{% endif %}>
|
| 985 |
-
<td>{{ voter.rank }}</td>
|
| 986 |
-
<td><a href="https://huggingface.co/{{ voter.username }}" target="_blank" rel="noopener">{{ voter.username }}</a></td>
|
| 987 |
-
<td>{{ voter.vote_count }}</td>
|
| 988 |
-
<td>{{ voter.join_date }}</td>
|
| 989 |
-
</tr>
|
| 990 |
-
{% endfor %}
|
| 991 |
-
</tbody>
|
| 992 |
-
</table>
|
| 993 |
-
</div>
|
| 994 |
-
{% else %}
|
| 995 |
-
<div class="no-data">
|
| 996 |
-
<p>No voters data available yet. Start voting to appear on the leaderboard!</p>
|
| 997 |
</div>
|
| 998 |
-
{% endif %}
|
| 999 |
|
| 1000 |
-
{% if top_voters %}
|
| 1001 |
<div class="thank-you-message">
|
| 1002 |
-
<p
|
| 1003 |
-
</div>
|
| 1004 |
-
{% endif %}
|
| 1005 |
-
|
| 1006 |
-
{% if not current_user.is_authenticated %}
|
| 1007 |
-
<div class="login-prompt">
|
| 1008 |
-
<p>Log in to appear on the leaderboard and track your voting stats!</p>
|
| 1009 |
-
<div class="button-container" style="margin-top: 16px;">
|
| 1010 |
-
<a href="{{ url_for('auth.login') }}" class="btn">Log In</a>
|
| 1011 |
-
</div>
|
| 1012 |
</div>
|
| 1013 |
-
{% endif %}
|
| 1014 |
</div>
|
| 1015 |
</div>
|
| 1016 |
|
|
@@ -1062,12 +1209,12 @@
|
|
| 1062 |
// Check URL hash for direct tab access
|
| 1063 |
function checkHashAndSetTab() {
|
| 1064 |
const hash = window.location.hash.toLowerCase();
|
| 1065 |
-
if (hash === '#tts' || hash === '' || hash === '#
|
| 1066 |
-
// Switch to TTS tab (default) or
|
| 1067 |
tabs.forEach(t => t.classList.remove('active'));
|
| 1068 |
tabContents.forEach(c => c.style.display = 'none');
|
| 1069 |
|
| 1070 |
-
const targetTab = hash === '#
|
| 1071 |
document.querySelector(`.tab[data-tab="${targetTab}"]`).classList.add('active');
|
| 1072 |
document.getElementById(`${targetTab}-tab`).style.display = 'block';
|
| 1073 |
|
|
|
|
| 709 |
color: var(--text-color);
|
| 710 |
}
|
| 711 |
|
| 712 |
+
/* Statistics styles */
|
| 713 |
+
.stats-container {
|
| 714 |
+
max-width: 800px;
|
| 715 |
+
margin: 0 auto;
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
.stats-title {
|
| 719 |
+
text-align: center;
|
| 720 |
+
font-size: 1.8rem;
|
| 721 |
+
margin-bottom: 32px;
|
| 722 |
+
color: var(--text-color);
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
.stats-grid {
|
| 726 |
+
display: grid;
|
| 727 |
+
grid-template-columns: repeat(2, 1fr);
|
| 728 |
+
gap: 20px;
|
| 729 |
+
margin-bottom: 32px;
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
@media (max-width: 600px) {
|
| 733 |
+
.stats-grid {
|
| 734 |
+
grid-template-columns: 1fr;
|
| 735 |
+
}
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
.stat-card {
|
| 739 |
+
display: flex;
|
| 740 |
+
align-items: center;
|
| 741 |
+
gap: 16px;
|
| 742 |
+
padding: 24px;
|
| 743 |
+
border-radius: 16px;
|
| 744 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
|
| 745 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
| 746 |
+
}
|
| 747 |
+
|
| 748 |
+
.stat-card:hover {
|
| 749 |
+
transform: translateY(-4px);
|
| 750 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
.stat-card.primary {
|
| 754 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 755 |
+
color: white;
|
| 756 |
+
}
|
| 757 |
+
|
| 758 |
+
.stat-card.secondary {
|
| 759 |
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
| 760 |
+
color: white;
|
| 761 |
+
}
|
| 762 |
+
|
| 763 |
+
.stat-card.accent {
|
| 764 |
+
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
| 765 |
+
color: white;
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
.stat-card.info {
|
| 769 |
+
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
| 770 |
+
color: white;
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
.stat-icon {
|
| 774 |
+
font-size: 2.5rem;
|
| 775 |
+
line-height: 1;
|
| 776 |
+
}
|
| 777 |
+
|
| 778 |
+
.stat-content {
|
| 779 |
+
display: flex;
|
| 780 |
+
flex-direction: column;
|
| 781 |
+
}
|
| 782 |
+
|
| 783 |
+
.stat-value {
|
| 784 |
+
font-size: 2.2rem;
|
| 785 |
+
font-weight: 700;
|
| 786 |
+
line-height: 1.1;
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
.stat-label {
|
| 790 |
+
font-size: 0.9rem;
|
| 791 |
+
opacity: 0.9;
|
| 792 |
+
margin-top: 4px;
|
| 793 |
+
}
|
| 794 |
+
|
| 795 |
+
.recent-activity {
|
| 796 |
+
background: var(--light-gray);
|
| 797 |
+
border-radius: 16px;
|
| 798 |
+
padding: 24px;
|
| 799 |
+
margin-bottom: 24px;
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
.recent-activity h3 {
|
| 803 |
+
margin: 0 0 16px 0;
|
| 804 |
+
font-size: 1.2rem;
|
| 805 |
+
color: var(--text-color);
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
.activity-stats {
|
| 809 |
+
display: flex;
|
| 810 |
+
gap: 24px;
|
| 811 |
+
}
|
| 812 |
+
|
| 813 |
+
@media (max-width: 480px) {
|
| 814 |
+
.activity-stats {
|
| 815 |
+
flex-direction: column;
|
| 816 |
+
gap: 12px;
|
| 817 |
+
}
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
.activity-item {
|
| 821 |
+
display: flex;
|
| 822 |
+
justify-content: space-between;
|
| 823 |
+
flex: 1;
|
| 824 |
+
padding: 12px 16px;
|
| 825 |
+
background: white;
|
| 826 |
+
border-radius: 10px;
|
| 827 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
.activity-label {
|
| 831 |
+
color: #666;
|
| 832 |
+
font-size: 0.9rem;
|
| 833 |
+
}
|
| 834 |
+
|
| 835 |
+
.activity-value {
|
| 836 |
+
font-weight: 600;
|
| 837 |
+
color: var(--primary-color);
|
| 838 |
+
}
|
| 839 |
+
|
| 840 |
+
@media (prefers-color-scheme: dark) {
|
| 841 |
+
.stat-card {
|
| 842 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
| 843 |
+
}
|
| 844 |
+
|
| 845 |
+
.stat-card:hover {
|
| 846 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
.recent-activity {
|
| 850 |
+
background: rgba(255, 255, 255, 0.05);
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
.activity-item {
|
| 854 |
+
background: rgba(255, 255, 255, 0.08);
|
| 855 |
+
}
|
| 856 |
+
|
| 857 |
+
.activity-label {
|
| 858 |
+
color: #aaa;
|
| 859 |
+
}
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
.disputed-badge {
|
| 863 |
display: inline-flex;
|
| 864 |
align-items: center;
|
|
|
|
| 915 |
{% block content %}
|
| 916 |
<div class="tabs">
|
| 917 |
<div class="tab active" data-tab="tts">TTS</div>
|
| 918 |
+
<div class="tab" data-tab="stats">Statistics</div>
|
| 919 |
</div>
|
| 920 |
|
| 921 |
<div id="tts-tab" class="tab-content">
|
|
|
|
| 1102 |
</div>
|
| 1103 |
</div>
|
| 1104 |
|
| 1105 |
+
<!-- Statistics Tab -->
|
| 1106 |
+
<div id="stats-tab" class="tab-content" style="display: none;">
|
| 1107 |
+
<div class="stats-container">
|
| 1108 |
+
<h2 class="stats-title">📊 Arena Statistics</h2>
|
| 1109 |
+
|
| 1110 |
+
<div class="stats-grid">
|
| 1111 |
+
<div class="stat-card primary">
|
| 1112 |
+
<div class="stat-icon">👥</div>
|
| 1113 |
+
<div class="stat-content">
|
| 1114 |
+
<div class="stat-value">{{ voting_stats.total_voters }}</div>
|
| 1115 |
+
<div class="stat-label">총 참여자 수</div>
|
| 1116 |
+
</div>
|
| 1117 |
+
</div>
|
| 1118 |
+
|
| 1119 |
+
<div class="stat-card secondary">
|
| 1120 |
+
<div class="stat-icon">🗳️</div>
|
| 1121 |
+
<div class="stat-content">
|
| 1122 |
+
<div class="stat-value">{{ voting_stats.total_votes }}</div>
|
| 1123 |
+
<div class="stat-label">총 투표 수</div>
|
| 1124 |
+
</div>
|
| 1125 |
+
</div>
|
| 1126 |
+
|
| 1127 |
+
<div class="stat-card accent">
|
| 1128 |
+
<div class="stat-icon">📈</div>
|
| 1129 |
+
<div class="stat-content">
|
| 1130 |
+
<div class="stat-value">{{ voting_stats.avg_votes_per_user }}</div>
|
| 1131 |
+
<div class="stat-label">인당 평균 투표</div>
|
| 1132 |
+
</div>
|
| 1133 |
+
</div>
|
| 1134 |
+
|
| 1135 |
+
<div class="stat-card info">
|
| 1136 |
+
<div class="stat-icon">🎙️</div>
|
| 1137 |
+
<div class="stat-content">
|
| 1138 |
+
<div class="stat-value">{{ voting_stats.active_models }}</div>
|
| 1139 |
+
<div class="stat-label">활성 TTS 모델</div>
|
| 1140 |
+
</div>
|
| 1141 |
</div>
|
|
|
|
| 1142 |
</div>
|
| 1143 |
|
| 1144 |
+
<div class="recent-activity">
|
| 1145 |
+
<h3>📅 최근 활동</h3>
|
| 1146 |
+
<div class="activity-stats">
|
| 1147 |
+
<div class="activity-item">
|
| 1148 |
+
<span class="activity-label">최근 24시간</span>
|
| 1149 |
+
<span class="activity-value">{{ voting_stats.votes_last_24h }} 투표</span>
|
| 1150 |
+
</div>
|
| 1151 |
+
<div class="activity-item">
|
| 1152 |
+
<span class="activity-label">최근 7일</span>
|
| 1153 |
+
<span class="activity-value">{{ voting_stats.votes_last_7d }} 투표</span>
|
| 1154 |
+
</div>
|
| 1155 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1156 |
</div>
|
|
|
|
| 1157 |
|
|
|
|
| 1158 |
<div class="thank-you-message">
|
| 1159 |
+
<p>🎉 한국어 TTS Arena에 참여해 주셔서 감사합니다! 여러분의 소중한 투표가 더 나은 TTS 모델 개발에 기여합니다.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1160 |
</div>
|
|
|
|
| 1161 |
</div>
|
| 1162 |
</div>
|
| 1163 |
|
|
|
|
| 1209 |
// Check URL hash for direct tab access
|
| 1210 |
function checkHashAndSetTab() {
|
| 1211 |
const hash = window.location.hash.toLowerCase();
|
| 1212 |
+
if (hash === '#tts' || hash === '' || hash === '#stats') {
|
| 1213 |
+
// Switch to TTS tab (default) or stats
|
| 1214 |
tabs.forEach(t => t.classList.remove('active'));
|
| 1215 |
tabContents.forEach(c => c.style.display = 'none');
|
| 1216 |
|
| 1217 |
+
const targetTab = hash === '#stats' ? 'stats' : 'tts';
|
| 1218 |
document.querySelector(`.tab[data-tab="${targetTab}"]`).classList.add('active');
|
| 1219 |
document.getElementById(`${targetTab}-tab`).style.display = 'block';
|
| 1220 |
|