KurtDu commited on
Commit
dff2993
1 Parent(s): 3a7b896

Upload 4 files

Browse files
Files changed (4) hide show
  1. app.py +173 -0
  2. elo_rank.py +133 -0
  3. index.html +568 -0
  4. requirements.txt +26 -0
app.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import random
4
+ import uuid
5
+ from flask import Flask, request, jsonify, session, render_template
6
+ from flask_cors import CORS
7
+ from datetime import datetime
8
+ from elo_rank import EloRank
9
+
10
+ app = Flask(__name__)
11
+ CORS(app)
12
+ app.secret_key = 'supersecretkey'
13
+
14
+ DATA_DIR = './data'
15
+ RESULTS_DIR = './results'
16
+
17
+ # 实例化 EloRank 系统
18
+ elo_rank_system = EloRank()
19
+
20
+ # 初始化 Elo 排名的模型
21
+ models = ['output_path_4o', 'output_path_miniomni', 'output_path_speechgpt', 'output_path_funaudio', 'output_path_4o_cascade', 'output_path_4o_llama_omni']
22
+ for model in models:
23
+ elo_rank_system.add_model(model)
24
+
25
+
26
+ def load_test_data(task):
27
+ """Load the JSON file corresponding to the selected task"""
28
+ with open(os.path.join(DATA_DIR, f"{task}.json"), "r", encoding='utf-8') as f:
29
+ test_data = json.load(f)
30
+
31
+ # 更新音频路径,将它们指向 Flask 静态文件夹
32
+ for item in test_data:
33
+ item['input_path'] = f"/static/audio{item['input_path']}"
34
+ item['output_path_4o'] = f"/static/audio{item['output_path_4o']}"
35
+ item['output_path_miniomni'] = f"/static/audio{item['output_path_miniomni']}"
36
+ item['output_path_speechgpt'] = f"/static/audio{item['output_path_speechgpt']}"
37
+ item['output_path_funaudio'] = f"/static/audio{item['output_path_funaudio']}"
38
+ item['output_path_4o_cascade'] = f"/static/audio{item['output_path_4o_cascade']}"
39
+ item['output_path_4o_llama_omni'] = f"/static/audio{item['output_path_4o_llama_omni']}"
40
+
41
+ return test_data
42
+
43
+
44
+ def save_result(task, username, result_data, session_id):
45
+ """Save user's result in a separate file"""
46
+ file_path = os.path.join(RESULTS_DIR, f"{task}_{username}_{session_id}.jsonl")
47
+ # 获取所有模型的 Elo 分数
48
+ elo_scores = {model: elo_rank_system.get_rating(model) for model in models}
49
+
50
+ # 添加 Elo 分数和时间戳到结果数据
51
+ result_data['elo_scores'] = elo_scores
52
+ result_data['timestamp'] = datetime.now().isoformat()
53
+ with open(file_path, "a", encoding='utf-8') as f:
54
+ f.write(json.dumps(result_data) + "\n")
55
+
56
+ @app.route('/start_test', methods=['POST'])
57
+ def start_test():
58
+ """Initiate the test for a user with the selected task"""
59
+ data = request.json
60
+ task = data['task']
61
+ username = data['username']
62
+
63
+ # Load the test data
64
+ test_data = load_test_data(task)
65
+
66
+ # Shuffle test data for the user
67
+ random.shuffle(test_data)
68
+
69
+ # Generate a unique session ID (for example using uuid)
70
+ session_id = str(uuid.uuid4())
71
+
72
+ # Store in session
73
+ session['task'] = task
74
+ session['username'] = username
75
+ session['test_data'] = test_data
76
+ session['current_index'] = 0
77
+ session['session_id'] = session_id # Store the session ID in the session
78
+
79
+ task_description = test_data[0].get('task_description', '')
80
+
81
+ return jsonify({"message": "Test started", "total_tests": len(test_data), "task_description": task_description})
82
+
83
+
84
+
85
+ @app.route('/next_test', methods=['GET'])
86
+ def next_test():
87
+ """Serve the next test item"""
88
+ if 'current_index' not in session or 'test_data' not in session:
89
+ return jsonify({"message": "No active test found"}), 400
90
+
91
+ current_index = session['current_index']
92
+ test_data = session['test_data']
93
+
94
+ if current_index >= len(test_data):
95
+ # Return the "Test completed" message when all tests are done
96
+ return jsonify({"message": "Test completed"}), 200
97
+
98
+ # 使用 EloRank 的 sample_next_match 来选择两款模型
99
+ selected_models = elo_rank_system.sample_next_match()
100
+
101
+ # Serve test data with the two selected models
102
+ current_test = test_data[current_index]
103
+ session['selected_models'] = selected_models
104
+ session['current_index'] += 1
105
+
106
+ return jsonify({
107
+ "text": current_test["text"],
108
+ "input_path": current_test["input_path"],
109
+ "model_a": selected_models[0],
110
+ "model_b": selected_models[1],
111
+ "audio_a": current_test[selected_models[0]],
112
+ "audio_b": current_test[selected_models[1]]
113
+ })
114
+
115
+ @app.route('/submit_result', methods=['POST'])
116
+ def submit_result():
117
+ """Submit the user's result and save it"""
118
+ data = request.json
119
+ chosen_model = data['chosen_model']
120
+
121
+ username = session.get('username')
122
+ task = session.get('task')
123
+ current_index = session.get('current_index') - 1 # Subtract since we increment after serving
124
+ session_id = session.get('session_id') # Get the session ID
125
+
126
+ if not username or not task or current_index < 0:
127
+ return jsonify({"message": "No active test found"}), 400
128
+
129
+ # Retrieve the selected models
130
+ selected_models = session['selected_models']
131
+ model_a = selected_models[0]
132
+ model_b = selected_models[1]
133
+
134
+ result = {
135
+ "name": username,
136
+ "chosen_model": chosen_model,
137
+ "model_a": model_a,
138
+ "model_b": model_b,
139
+ "result": {
140
+ model_a: 1 if chosen_model == 'A' else 0,
141
+ model_b: 1 if chosen_model == 'B' else 0
142
+ }
143
+ }
144
+
145
+ # Save the result for the current test using session_id to avoid filename conflict
146
+ test_data = session['test_data'][current_index]
147
+ result_data = {**test_data, **result}
148
+ save_result(task, username, result_data, session_id)
149
+
150
+ # 更新 Elo 排名系统
151
+ if chosen_model == 'A':
152
+ elo_rank_system.record_match(model_a, model_b)
153
+ else:
154
+ elo_rank_system.record_match(model_b, model_a)
155
+
156
+ return jsonify({"message": "Result submitted", "model_a": model_a, "model_b": model_b, "chosen_model": chosen_model})
157
+
158
+ @app.route('/end_test', methods=['GET'])
159
+ def end_test():
160
+ """End the test session"""
161
+ session.clear()
162
+ return jsonify({"message": "Test completed"})
163
+
164
+ # 渲染index.html页面
165
+ @app.route('/')
166
+ def index():
167
+ return render_template('index.html')
168
+
169
+ if __name__ == '__main__':
170
+ if not os.path.exists(RESULTS_DIR):
171
+ os.makedirs(RESULTS_DIR)
172
+ # 允许局域网访问
173
+ app.run(debug=True, host="0.0.0.0", port=6002)
elo_rank.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import json
3
+
4
+ class EloRank:
5
+ def __init__(self, initial_rating=1000, k_factor=32):
6
+ """
7
+ Initialize the EloRank class.
8
+ :param initial_rating: Initial ELO rating for each model.
9
+ :param k_factor: The K-factor that determines the sensitivity of rating changes.
10
+ """
11
+ self.ratings = {}
12
+ self.initial_rating = initial_rating
13
+ self.k_factor = k_factor
14
+ self.wins = {}
15
+
16
+ def add_model(self, model_id):
17
+ """
18
+ Add a new model with the initial rating.
19
+ :param model_id: Unique identifier for the model.
20
+ """
21
+ self.ratings[model_id] = self.initial_rating
22
+ self.wins[model_id] = 0
23
+
24
+ def record_match(self, winner, loser):
25
+ """
26
+ Update the ratings based on a match result.
27
+ :param winner: Model ID of the winner.
28
+ :param loser: Model ID of the loser.
29
+ """
30
+ rating_winner = self.ratings[winner]
31
+ rating_loser = self.ratings[loser]
32
+
33
+ expected_winner = self.expected_score(rating_winner, rating_loser)
34
+ expected_loser = self.expected_score(rating_loser, rating_winner)
35
+
36
+ self.ratings[winner] += self.k_factor * (1 - expected_winner)
37
+ self.ratings[loser] += self.k_factor * (0 - expected_loser)
38
+
39
+ # Update win count
40
+ self.wins[winner] += 1
41
+
42
+ def expected_score(self, rating_a, rating_b):
43
+ """
44
+ Calculate the expected score for a model.
45
+ :param rating_a: Rating of model A.
46
+ :param rating_b: Rating of model B.
47
+ :return: Expected score.
48
+ """
49
+ return 1 / (1 + 10 ** ((rating_b - rating_a) / 400))
50
+
51
+ def get_rating(self, model_id):
52
+ """
53
+ Get the current rating of a model.
54
+ :param model_id: Unique identifier for the model.
55
+ :return: Current rating of the model.
56
+ """
57
+ return self.ratings.get(model_id, None)
58
+
59
+ def get_wins(self, model_id):
60
+ """
61
+ Get the number of wins of a model.
62
+ :param model_id: Unique identifier for the model.
63
+ :return: Number of wins of the model.
64
+ """
65
+ return self.wins.get(model_id, 0)
66
+
67
+ def get_top_models(self, n=2):
68
+ """
69
+ Get the top N models by rating.
70
+ :param n: Number of top models to retrieve.
71
+ :return: List of model IDs of the top models.
72
+ """
73
+ return sorted(self.ratings, key=self.ratings.get, reverse=True)[:n]
74
+
75
+ def sample_next_match(self):
76
+ """
77
+ Sample the next match based on the probability proportional to the current rating.
78
+ This approach helps accelerate the convergence of ranking.
79
+ :return: Tuple of two model IDs for the next match.
80
+ """
81
+ model_ids = list(self.ratings.keys())
82
+ probabilities = [self.ratings[model_id] for model_id in model_ids]
83
+ total_rating = sum(probabilities)
84
+ probabilities = [rating / total_rating for rating in probabilities]
85
+
86
+ # Sample two different models for the next match
87
+ next_match = random.choices(model_ids, probabilities, k=2)
88
+ while next_match[0] == next_match[1]:
89
+ next_match = random.choices(model_ids, probabilities, k=2)
90
+
91
+ return tuple(next_match)
92
+
93
+ def process_match_records(self, file_path):
94
+ """
95
+ Process match records from a JSON file and update ratings and win counts accordingly.
96
+ :param file_path: Path to the JSON file containing match records.
97
+ """
98
+ with open(file_path, 'r') as file:
99
+ match_records = json.load(file)
100
+
101
+ for record in match_records:
102
+ winner = record['winner']
103
+ model_1 = record['model_1']
104
+ model_2 = record['model_2']
105
+
106
+ # Add models if they are not already added
107
+ if model_1 not in self.ratings:
108
+ self.add_model(model_1)
109
+ if model_2 not in self.ratings:
110
+ self.add_model(model_2)
111
+
112
+ # Record the match result
113
+ if winner == model_1:
114
+ self.record_match(model_1, model_2)
115
+ elif winner == model_2:
116
+ self.record_match(model_2, model_1)
117
+
118
+ # # Example Usage
119
+ # e = EloRank()
120
+ # e.add_model('model_A')
121
+ # e.add_model('model_B')
122
+ # e.add_model('model_C')
123
+
124
+ # e.record_match('model_A', 'model_B')
125
+ # print(e.get_rating('model_A')) # Should be greater than the initial rating
126
+ # print(e.get_rating('model_B')) # Should be less than the initial rating
127
+
128
+ # print(e.get_top_models(2)) # Get the top 2 models
129
+ # print(e.sample_next_match()) # Sample the next match based on ratings
130
+
131
+ # # Process match records from a JSON file
132
+ # e.process_match_records('match_records.json')
133
+ # print(e.get_wins('model_A')) # Get the number of wins for model_A
index.html ADDED
@@ -0,0 +1,568 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Speech-to-Speech Model Comparison</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
10
+ <style>
11
+ body {
12
+ background-color: #f4f6f9;
13
+ font-family: 'Arial', sans-serif;
14
+ }
15
+
16
+ .container {
17
+ background-color: white;
18
+ border-radius: 10px;
19
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
20
+ padding: 30px;
21
+ }
22
+
23
+ h3 {
24
+ font-size: 1.2rem;
25
+ /* 调整标题字体大小 */
26
+ font-weight: bold;
27
+ color: #333;
28
+ }
29
+
30
+ .form-control {
31
+ border-radius: 25px;
32
+ padding: 15px;
33
+ }
34
+
35
+ .btn {
36
+ border-radius: 25px;
37
+ font-size: 0.9rem;
38
+ padding: 8px 16px;
39
+ transition: background-color 0.3s ease;
40
+ }
41
+
42
+ .btn-primary {
43
+ background-color: #007bff;
44
+ border: none;
45
+ }
46
+
47
+ .btn-primary:hover {
48
+ background-color: #0056b3;
49
+ }
50
+
51
+ .btn-success {
52
+ background-color: #28a745;
53
+ border: none;
54
+ }
55
+
56
+ .btn-success:hover {
57
+ background-color: #218838;
58
+ }
59
+
60
+ .btn-selected {
61
+ background-color: #155724 !important;
62
+ color: white !important;
63
+ }
64
+
65
+ .btn-option {
66
+ font-size: 0.9rem;
67
+ padding: 8px 20px;
68
+ margin: 0 10px;
69
+ }
70
+
71
+ #test-content {
72
+ display: none;
73
+ }
74
+
75
+ #category-select,
76
+ #task-select-dropdown {
77
+ width: 120% !important;
78
+ /* 设置宽度为60% */
79
+ margin: 0 auto;
80
+ /* 居中对齐 */
81
+ }
82
+
83
+ #confirm-choice,
84
+ #next-test {
85
+ display: none;
86
+ transition: opacity 0.3s ease;
87
+ }
88
+
89
+ #model-comparison {
90
+ display: none;
91
+ opacity: 0;
92
+ transition: opacity 0.3s ease;
93
+ }
94
+
95
+ #model-comparison.show {
96
+ opacity: 1;
97
+ }
98
+
99
+ #switch-task {
100
+ font-size: 0.8rem;
101
+ padding: 5px 10px;
102
+ position: absolute;
103
+ top: 10px;
104
+ right: 20px;
105
+ display: none;
106
+ }
107
+ #task-description {
108
+ display: none;
109
+ }
110
+ </style>
111
+ </head>
112
+
113
+ <body>
114
+ <div class="container py-5">
115
+ <h3 class="text-center mb-4">Speech-to-Speech Model Comparison</h3>
116
+
117
+ <div id="evaluation-info" class="mb-5">
118
+ <p class="text-start">
119
+ <strong>Welcome to the Speech-to-Speech (S2S) Model Evaluation!</strong>
120
+ <br><br>
121
+ In this evaluation, you will assess the performance of 4 S2S models:
122
+ <strong>ChatGPT-4o</strong>, <strong>FunAudioLLM</strong>, <strong>SpeechGPT</strong>, and
123
+ <strong>Mini-Omni</strong>.
124
+ The goal is to evaluate how well these models handle various speech tasks across different domains.
125
+ <br><br>
126
+ Once you select a specific domain and task (e.g., <em>Educational Tutoring</em> and <em>Rhythm Control</em>),
127
+ you will proceed to the evaluation stage. In each round, you will be presented with an audio input.
128
+ For example:
129
+ <br><br>
130
+
131
+ <!-- Left-aligned Audio Sample and Audio Control -->
132
+ <span style="vertical-align: middle; line-height: 1.2; display: inline-block;"><strong>Audio Sample:</strong></span>
133
+ <audio controls style="vertical-align: middle;">
134
+ <source src="/static/audio/sample/input_audio.wav" type="audio/wav">
135
+ </audio>
136
+
137
+ <br><br>
138
+ The corresponding text is:
139
+ <em>"Say the following sentence at my speed first, then say it again very slowly:
140
+ 'Artificial intelligence is changing the world in many ways.'" </em>
141
+ <small>(Note: the audio plays at 1.5x the normal speed.)</small>
142
+ <br><br>
143
+ The responses of different S2S models will be provided, and your task is to choose which response best follows
144
+ the instructions. For example<small>(Note: During the evaluation process, you will be provided with responses from only the two models that have the most comparative significance.)</small>:
145
+ <br><br>
146
+
147
+ <!-- ChatGPT-4o Output -->
148
+ <span><strong>ChatGPT-4o:</strong></span>
149
+ <audio controls style="vertical-align: middle;">
150
+ <source src="/static/audio/sample/4o_audio.wav" type="audio/wav">
151
+ </audio>
152
+ <p class="text-start" style="margin-left: 20px;">
153
+ <strong>Performance:</strong> Speech: Partially followed the instruction on speed. Semantics: Accurately followed the instruction, with no semantic deviation or missing information.
154
+ </p>
155
+
156
+ <!-- FunAudioLLM Output -->
157
+ <span><strong>FunAudioLLM:</strong></span>
158
+ <audio controls style="vertical-align: middle;">
159
+ <source src="/static/audio/sample/FunAudio_audio.wav" type="audio/wav">
160
+ </audio>
161
+ <p class="text-start" style="margin-left: 20px;">
162
+ <strong>Performance:</strong> Speech: Partially followed the instruction on speed. Semantics: Accurately followed the instruction, with no semantic deviation or missing information.
163
+ </p>
164
+
165
+ <!-- SpeechGPT Output -->
166
+ <span><strong>SpeechGPT:</strong></span>
167
+ <audio controls style="vertical-align: middle;">
168
+ <source src="/static/audio/sample/SpeechGPT.wav" type="audio/wav">
169
+ </audio>
170
+ <p class="text-start" style="margin-left: 20px;">
171
+ <strong>Performance:</strong> Speech: Did not follow the instruction on speed. Semantics: Partially followed the instruction, with minor semantic deviation and missing information.
172
+ </p>
173
+
174
+ <!-- Mini-Omni Output -->
175
+ <span><strong>Mini-Omni:</strong></span>
176
+ <audio controls style="vertical-align: middle;">
177
+ <source src="/static/audio/sample/mini-omni.wav" type="audio/wav">
178
+ </audio>
179
+ <p class="text-start" style="margin-left: 20px;">
180
+ <strong>Performance:</strong> Speech: Did not follow the instruction on speed. Semantics: Did not follow the instruction, with significant semantic deviation and missing information.
181
+ </p>
182
+
183
+ <p class="text-start">
184
+ After making your choice, you'll proceed to the next round.
185
+ </p>
186
+ <strong>Please enter your username and start the evaluation!</strong>
187
+ </p>
188
+ </div>
189
+
190
+ <div id="user-input" class="text-center">
191
+ <div class="mb-3">
192
+ <input type="text" id="username" class="form-control w-50 mx-auto" placeholder="Your username" />
193
+ </div>
194
+ <button class="btn btn-primary" onclick="startTest()">Start Test</button>
195
+ </div>
196
+
197
+
198
+ <div id="task-select" class="text-center" style="display: none;">
199
+ <h3 class="my-4">Select Test Category:</h3>
200
+ <div class="d-grid gap-2 col-6 mx-auto">
201
+ <!-- Category dropdown -->
202
+ <select id="category-select" class="form-select mx-auto" onchange="populateTasks()">
203
+ <option value="" disabled selected>Select Category</option>
204
+ <option value="educational">Educational Tutoring</option>
205
+ <option value="social">Social Companionship</option>
206
+ <option value="entertainment">Entertainment Dubbing</option>
207
+ <option value="medical">Medical Consultation</option>
208
+ </select>
209
+ </div>
210
+
211
+ <h3 class="my-4" id="specific-task-title" style="display: none;">Select Specific Task:</h3>
212
+ <div class="d-grid gap-2 col-6 mx-auto">
213
+ <!-- Task dropdown -->
214
+ <select id="task-select-dropdown" class="form-select mx-auto" style="display: none;">
215
+ <option value="" disabled selected>Select Specific Task</option>
216
+ <!-- Options will be populated dynamically -->
217
+ </select>
218
+ </div>
219
+
220
+ <button class="btn btn-primary mt-4" id="start-task-btn" onclick="selectTaskFromDropdown()"
221
+ style="display: none;">Start Task</button>
222
+ </div>
223
+
224
+ <button id="switch-task" class="btn btn-warning" onclick="switchTask()">Switch Category and Tasks</button>
225
+
226
+ <div id="test-content">
227
+ <div class="text-center">
228
+
229
+ <div class="row justify-content-center">
230
+ <div class="col-md-6 text-start double-text" style="margin-bottom: 10px;">
231
+ <strong>Task description:</strong> <span id="task-description"></span>
232
+ </div>
233
+ </div>
234
+
235
+ <!-- 在音频控件前添加粗体的 Audio: -->
236
+ <div class="row justify-content-center">
237
+ <div class="col-md-6 d-flex justify-content-center align-items-center mb-4">
238
+ <strong class="me-2">Audio:</strong> <!-- 加粗的 Audio 标签 -->
239
+ <audio id="input-audio" controls></audio>
240
+ </div>
241
+ </div>
242
+
243
+ <div class="row justify-content-center">
244
+ <div class="col-md-6 text-start double-text" style="margin-bottom: 10px;">
245
+ <strong>Audio text:</strong> <span id="test-text"></span>
246
+ </div>
247
+ </div>
248
+
249
+ <!-- 调整后的左对齐样式 -->
250
+ <div class="row justify-content-center">
251
+ <div class="col-md-6 text-start">
252
+ <p><strong>Question:</strong> Which of the following two models answers the result better?</p>
253
+ </div>
254
+ </div>
255
+
256
+ <!-- 使用flex布局放置音频 -->
257
+ <div class="mb-4 text-center">
258
+ <div class="model-section d-flex align-items-center justify-content-center mb-3">
259
+ <h6 class="me-2" style="margin-bottom: 0; margin-top: 5px; font-weight: bold;">Model A:</h6>
260
+ <audio id="audio-a" controls></audio>
261
+ </div>
262
+ <div class="model-section d-flex align-items-center justify-content-center">
263
+ <h6 class="me-2" style="margin-bottom: 0; margin-top: 5px; font-weight: bold;">Model B:</h6>
264
+ <audio id="audio-b" controls></audio>
265
+ </div>
266
+ </div>
267
+
268
+
269
+ <div class="d-flex justify-content-center mt-4">
270
+ <button class="btn btn-success btn-option mx-2" onclick="selectModel('A')">Model A</button>
271
+ <button class="btn btn-success btn-option mx-2" onclick="selectModel('B')">Model B</button>
272
+ </div>
273
+
274
+ <div id="model-comparison" class="text-center mt-4">
275
+ <p>Model A: <span id="model-a"></span></p>
276
+ <p>Model B: <span id="model-b"></span></p>
277
+ <p>Your choice: <span id="chosen-model"></span></p>
278
+ </div>
279
+
280
+ <button id="confirm-choice" class="btn btn-primary mt-4" onclick="confirmChoice()">Confirm
281
+ Selection</button>
282
+ <button id="next-test" class="btn btn-primary mt-4" onclick="loadNextTest()">Next Test</button>
283
+ </div>
284
+ </div>
285
+
286
+ <div id="test-completed" class="text-center" style="display: none;">
287
+ <h3>Thank you for completing the <span id="completed-task"></span> test!</h3>
288
+ <p>Would you like to test another category or task?</p>
289
+ <button class="btn btn-primary" onclick="switchTask()">Yes</button>
290
+ <button class="btn btn-secondary" onclick="endTest()">No</button>
291
+ </div>
292
+
293
+ <!-- 引入 Bootstrap 脚本 -->
294
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
295
+ <script>
296
+ let username;
297
+ let task;
298
+ let chosenModel;
299
+ let modelA, modelB; // 存储映射后的模型名称
300
+
301
+ // 根据模型路径显示友好的模型名称
302
+ const modelNames = {
303
+ "output_path_speechgpt": "SpeechGPT",
304
+ "output_path_miniomni": "Mini-Omni",
305
+ "output_path_4o": "ChatGPT-4o",
306
+ "output_path_funaudio": "FunAudioLLM",
307
+ "output_path_4o_cascade": "Cascade",
308
+ "output_path_4o_llama_omni": "LLaMA-Omni"
309
+ };
310
+
311
+ function startTest() {
312
+ username = $("#username").val();
313
+ if (!username) {
314
+ alert("Please enter a username");
315
+ return;
316
+ }
317
+ $("#evaluation-info").hide();
318
+ $("#user-input").hide();
319
+ $("#task-select").show();
320
+ }
321
+
322
+ function switchTask() {
323
+ // 清理页面所有之前的测试内容
324
+ $("#task-description").text('');
325
+ $("#test-content").hide(); // 隐藏测试内容
326
+ $("#test-text").text(''); // 清空音频文本
327
+ $("#input-audio").attr("src", ''); // 清空音频路径
328
+ $("#audio-a").attr("src", ''); // 清空模型 A 音频
329
+ $("#audio-b").attr("src", ''); // 清空模型 B 音频
330
+ $("#chosen-model").text(''); // 清空用户选择的模型显示
331
+ $("#model-a").text(''); // 清空模型 A 名称显示
332
+ $("#model-b").text(''); // 清空模型 B 名称显示
333
+ $("#confirm-choice").hide(); // 隐藏确认选择按钮
334
+ $("#next-test").hide(); // 隐藏下一个测试按钮
335
+ $("#model-comparison").removeClass('show').hide(); // 隐藏模型对比部分
336
+
337
+ // 显示选择任务的页面
338
+ $("#test-completed").hide(); // 隐藏感谢页面
339
+ $("#task-select").show(); // 显示选择方向页面
340
+ $("#switch-task").hide(); // 隐藏切换按钮直到新测试开始
341
+ }
342
+
343
+ function selectTask(selectedTask) {
344
+ task = selectedTask;
345
+
346
+ // 切换任务时清理页面内容,防止旧数据残留
347
+ $("#task-description").text('');
348
+ $("#test-text").text(''); // 清空音频文本
349
+ $("#input-audio").attr("src", ''); // 清空音频路径
350
+ $("#audio-a").attr("src", ''); // 清空模型 A 音频
351
+ $("#audio-b").attr("src", ''); // 清空模型 B 音频
352
+ $("#chosen-model").text(''); // 清空用户选择的模型显示
353
+ $("#model-a").text(''); // 清空模型 A 名称显示
354
+ $("#model-b").text(''); // 清空模型 B 名称显示
355
+ $("#confirm-choice").hide(); // 隐藏确认选择按钮
356
+ $("#next-test").hide(); // 隐藏下一个测试按钮
357
+ $("#model-comparison").removeClass('show').hide(); // 隐藏模型对比部分
358
+
359
+ // 隐藏选择任务界面,显示切换方向按钮
360
+ $("#task-select").hide();
361
+ $("#switch-task").show();
362
+
363
+ // 发起请求获取新的测试数据
364
+ $.ajax({
365
+ url: '/start_test',
366
+ type: 'POST',
367
+ contentType: 'application/json',
368
+ data: JSON.stringify({ username: username, task: task }),
369
+ success: function (data) {
370
+ $("#test-content").show();
371
+ loadNextTest();
372
+ },
373
+ error: function (xhr, status, error) {
374
+ console.error("Error occurred: ", status, error);
375
+ }
376
+ });
377
+ }
378
+
379
+ function populateTasks() {
380
+ const category = $("#category-select").val();
381
+ const taskDropdown = $("#task-select-dropdown");
382
+
383
+ // 清空任务下拉菜单
384
+ taskDropdown.empty();
385
+ // 添加禁用的默认选项
386
+ taskDropdown.append('<option value="" disabled selected>Select Specific Task</option>');
387
+
388
+ // 根据所选分类填充相应的任务选项
389
+ if (category === 'educational') {
390
+ taskDropdown.append('<option value="pronunciation">Correcting pronunciation ability</option>');
391
+ taskDropdown.append('<option value="rhythm">Rhythm control capabilities</option>');
392
+ taskDropdown.append('<option value="translation">Cross-language translation with emotion</option>');
393
+ taskDropdown.append('<option value="language">Language consistency</option>');
394
+ taskDropdown.append('<option value="pause">Pause and segmentation</option>');
395
+ taskDropdown.append('<option value="polyphone">Polyphonic word comprehension</option>');
396
+ taskDropdown.append('<option value="stress">Emphasis control</option>');
397
+ } else if (category === 'social') {
398
+ taskDropdown.append('<option value="emotion">Emotion recognition and expression</option>');
399
+ taskDropdown.append('<option value="identity">Identity coping ability</option>');
400
+ taskDropdown.append('<option value="humor">Implications ability</option>');
401
+ taskDropdown.append('<option value="irony">Sarcasm detection</option>');
402
+ } else if (category === 'entertainment') {
403
+ taskDropdown.append('<option value="natural">Ability to simulate natural sound</option>');
404
+ taskDropdown.append('<option value="singing">Singing ability</option>');
405
+ taskDropdown.append('<option value="tongue">Tongue twisters capabilities</option>');
406
+ taskDropdown.append('<option value="crosstalk">Crosstalk ability</option>');
407
+ taskDropdown.append('<option value="poetry">Poetry recitation</option>');
408
+ taskDropdown.append('<option value="role">Role-playing</option>');
409
+ taskDropdown.append('<option value="story">Storytelling</option>');
410
+ } else if (category === 'medical') {
411
+ taskDropdown.append('<option value="healthcare">Health consultation</option>');
412
+ taskDropdown.append('<option value="illness">Querying symptoms</option>');
413
+ taskDropdown.append('<option value="psychological">Psychological comfort</option>');
414
+ }
415
+
416
+ // 显示任务下拉菜单和开始按钮
417
+ if (category) {
418
+ $("#specific-task-title").show();
419
+ $("#task-select-dropdown").show();
420
+ $("#start-task-btn").show();
421
+ } else {
422
+ $("#specific-task-title").hide();
423
+ $("#task-select-dropdown").hide();
424
+ $("#start-task-btn").hide();
425
+ }
426
+ }
427
+
428
+
429
+ function selectTaskFromDropdown() {
430
+ const selectedTask = $("#task-select-dropdown").val();
431
+ if (selectedTask) {
432
+ task = selectedTask;
433
+ $.ajax({
434
+ url: '/start_test',
435
+ type: 'POST',
436
+ contentType: 'application/json',
437
+ data: JSON.stringify({ username: username, task: task }),
438
+ success: function (data) {
439
+ // 在页面显示任务描述
440
+ $("#task-description").text(data.task_description);
441
+ $("#task-description").show();
442
+ $("#task-select").hide();
443
+ $("#test-content").show();
444
+ $("#switch-task").show(); // 显示右上角的切换任务按钮
445
+ loadNextTest();
446
+ },
447
+ error: function (xhr, status, error) {
448
+ console.error("Error occurred: ", status, error);
449
+ }
450
+ });
451
+ } else {
452
+ alert("Please select a specific task.");
453
+ }
454
+ }
455
+
456
+
457
+
458
+
459
+ function loadNextTest() {
460
+ $.get('/next_test', function (data) {
461
+ if (data.message === 'Test completed') {
462
+ $("#test-content").hide(); // 隐藏测试内容
463
+ $("#test-completed").show(); // 显示测试完成的页面
464
+
465
+ // 动态显示完成的任务名称
466
+ $("#completed-task").text(task);
467
+
468
+ // 清空 session 数据,防止继续原方向的测试
469
+ sessionStorage.removeItem('current_index');
470
+ } else {
471
+ // 正常加载测试题目
472
+ console.log(data); // 添加调试信息,检查 data 的内容
473
+ $("#task-description").text(data.task_description);
474
+ $("#test-text").text(data.text);
475
+ $("#input-audio").attr("src", data.input_path);
476
+ $("#audio-a").attr("src", data.audio_a);
477
+ $("#audio-b").attr("src", data.audio_b);
478
+
479
+ // 更新模型信息
480
+ modelA = modelNames[data.model_a];
481
+ modelB = modelNames[data.model_b];
482
+ $("#model-a").text(modelA);
483
+ $("#model-b").text(modelB);
484
+
485
+ $("#next-test").hide();
486
+ $("#model-comparison").hide();
487
+ $("#confirm-choice").show();
488
+ chosenModel = null;
489
+ $(".btn-option").prop('disabled', false);
490
+ $(".btn-option").removeClass("btn-selected").addClass("btn-success");
491
+ }
492
+ }, 'json').fail(function (xhr, status, error) {
493
+ console.error("Failed to load test data:", status, error);
494
+ });
495
+ }
496
+
497
+ function endTest() {
498
+ // 用户选择结束测试,可以跳转到结束页面或者返回主页
499
+ alert("Thank you for participating in the test!");
500
+ // 或者可以通过 window.location.href 跳转到其他页面
501
+ window.location.href = "/thank_you"; // 你可以设置一个感谢页面或者其他动作
502
+ }
503
+
504
+ function selectModel(model) {
505
+ // 将用户选择的模型存储在变量 chosenModel 中
506
+ chosenModel = model;
507
+
508
+ // 禁用所有模型选择按钮,防止重复点击
509
+ $(".btn-option").prop('disabled', false); // 允许用户重新选择
510
+
511
+ // 重置所有按钮的样式
512
+ $(".btn-option").removeClass("btn-selected").addClass("btn-success");
513
+
514
+ // 根据用户选择的模型按钮,添加 btn-selected 类改变其样式
515
+ if (model === 'A') {
516
+ $("button:contains('Model A')").removeClass("btn-success").addClass("btn-selected");
517
+ } else if (model === 'B') {
518
+ $("button:contains('Model B')").removeClass("btn-success").addClass("btn-selected");
519
+ }
520
+ }
521
+
522
+ function confirmChoice() {
523
+ // 检查是否选择了模型,如果没有选择,则提示用户
524
+ if (!chosenModel) {
525
+ alert("Please select a model before confirming.");
526
+ return;
527
+ }
528
+
529
+ // 禁用模型选择按钮,防止用户在确认选择后更改选择
530
+ $(".btn-option").prop('disabled', true);
531
+
532
+ // 一旦用户确认选择,立刻显示选择的详细信息
533
+ if (chosenModel === 'A') {
534
+ $("#chosen-model").text(modelA); // 使用 modelA 映射后的名称
535
+ } else {
536
+ $("#chosen-model").text(modelB); // 使用 modelB 映射后的名称
537
+ }
538
+
539
+ // 将模型 A 和 B 的名称显示在同一行中,包括中文和英文翻译
540
+ $("#model-a").text(modelA); // 模型 A 名称
541
+ $("#model-b").text(modelB); // 模型 B 名称
542
+
543
+ // 确认选择后才显示模型详细信息
544
+ $("#model-comparison").addClass('show');
545
+ $("#model-comparison").show(); // 展示选择信息
546
+
547
+ // 隐藏确认按钮,显示“Next Test”按钮
548
+ $("#confirm-choice").hide();
549
+ $("#next-test").show();
550
+
551
+ // 提交用户选择的模型
552
+ $.ajax({
553
+ url: '/submit_result',
554
+ type: 'POST',
555
+ contentType: 'application/json',
556
+ data: JSON.stringify({ chosen_model: chosenModel }),
557
+ success: function (data) {
558
+ // 成功提交后处理逻辑
559
+ },
560
+ error: function (xhr, status, error) {
561
+ console.error("Error occurred: ", status, error);
562
+ }
563
+ });
564
+ }
565
+ </script>
566
+ </body>
567
+
568
+ </html>
requirements.txt ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ blinker==1.8.2
2
+ click==8.1.7
3
+ contourpy==1.3.0
4
+ cycler==0.12.1
5
+ Flask==3.0.3
6
+ Flask-Cors==5.0.0
7
+ fonttools==4.54.1
8
+ importlib_metadata==8.5.0
9
+ importlib_resources==6.4.5
10
+ itsdangerous==2.2.0
11
+ Jinja2==3.1.4
12
+ kiwisolver==1.4.7
13
+ MarkupSafe==3.0.2
14
+ matplotlib==3.9.2
15
+ numpy==2.1.2
16
+ packaging==24.1
17
+ pandas==2.2.3
18
+ pillow==11.0.0
19
+ pyparsing==3.2.0
20
+ python-dateutil==2.9.0.post0
21
+ pytz==2024.2
22
+ seaborn==0.13.2
23
+ six==1.16.0
24
+ tzdata==2024.2
25
+ Werkzeug==3.0.5
26
+ zipp==3.20.2