Ko-TTS-Arena Contributors commited on
Commit
88cf8f6
·
1 Parent(s): f49c679
Files changed (3) hide show
  1. app.py +2 -2
  2. models.py +37 -0
  3. 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
- top_voters = get_top_voters(10) # Get top 10 voters
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
- top_voters=top_voters,
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="voters">Top Voters</div>
769
  </div>
770
 
771
  <div id="tts-tab" class="tab-content">
@@ -952,65 +1102,62 @@
952
  </div>
953
  </div>
954
 
955
- <!-- Add Top Voters Tab -->
956
- <div id="voters-tab" class="tab-content" style="display: none;">
957
- <div class="voters-leaderboard">
958
- <div class="voters-leaderboard-header">
959
- <h2>Top Voters</h2>
960
- {% if current_user.is_authenticated %}
961
- <div class="visibility-toggle">
962
- <span class="toggle-label">Show me in leaderboard</span>
963
- <label class="toggle-switch">
964
- <input type="checkbox" id="visibility-toggle" {% if user_leaderboard_visibility %}checked{% endif %}>
965
- <span class="toggle-slider"></span>
966
- </label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
967
  </div>
968
- {% endif %}
969
  </div>
970
 
971
- {% if top_voters %}
972
- <div class="leaderboard-container">
973
- <table class="voters-table">
974
- <thead>
975
- <tr>
976
- <th>Rank</th>
977
- <th>Username</th>
978
- <th>Total Votes</th>
979
- <th>Joined</th>
980
- </tr>
981
- </thead>
982
- <tbody>
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>Thank you to all our voters for helping improve TTS Arena! Your contributions make this community better.</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 === '#voters') {
1066
- // Switch to TTS tab (default) or voters
1067
  tabs.forEach(t => t.classList.remove('active'));
1068
  tabContents.forEach(c => c.style.display = 'none');
1069
 
1070
- const targetTab = hash === '#voters' ? 'voters' : 'tts';
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