rein0421 commited on
Commit
da3d356
·
verified ·
1 Parent(s): f514ea6

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +315 -320
templates/index.html CHANGED
@@ -1,321 +1,316 @@
1
- <!DOCTYPE html>
2
- <html lang="ja">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Voice Recorder Interface</title>
7
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- </head>
10
- <body class="flex flex-col items-center justify-center h-screen bg-gray-900 text-white">
11
- <!-- メンバーを登録ボタン -->
12
- <div class="flex items-center mb-5">
13
- <button
14
- id="registerButton"
15
- onclick="showUserRegister()"
16
- class="px-4 py-2 bg-blue-600 rounded-md hover:bg-blue-700 transition"
17
- >
18
- メンバーを登録
19
- </button>
20
- </div>
21
-
22
- <!-- チャート表示部 -->
23
- <div class="chart w-72 h-72 mb-5">
24
- <canvas id="speechChart"></canvas>
25
- </div>
26
-
27
- <!-- 録音フォーム -->
28
- <form
29
- id="recordForm"
30
- action="/submit"
31
- method="POST"
32
- class="flex items-center space-x-2 w-full sm:w-auto"
33
- onsubmit="event.preventDefault();"
34
- >
35
- <input
36
- type="text"
37
- name="name"
38
- placeholder="名前を入力"
39
- class="flex-1 px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-gray-700 text-white"
40
- />
41
- <!-- 録音ボタン -->
42
- <button type="button" class="record-button" id="recordButton" onclick="toggleRecording()">
43
- <div class="record-icon" id="recordIcon"></div>
44
- </button>
45
- </form>
46
-
47
- <!-- 結果ボタン -->
48
- <div class="flex mt-5">
49
- <button
50
- id="historyButton"
51
- onclick="showTalkdetail()"
52
- class="result-button px-4 py-2 mx-2 bg-green-600 rounded-md hover:bg-green-700 transition"
53
- >
54
- 会話履歴を表示
55
- </button>
56
- <button
57
- id="feedbackButton"
58
- onclick="showResults()"
59
- class="result-button px-4 py-2 mx-2 bg-blue-600 rounded-md hover:bg-blue-700 transition"
60
- >
61
- フィードバック画面を表示
62
- </button>
63
- </div>
64
-
65
- <style>
66
- .record-button {
67
- width: 80px;
68
- height: 80px;
69
- background-color: transparent;
70
- border-radius: 50%;
71
- border: 4px solid white;
72
- display: flex;
73
- justify-content: center;
74
- align-items: center;
75
- cursor: pointer;
76
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
77
- transition: all 0.2s ease;
78
- }
79
- .record-icon {
80
- width: 60px;
81
- height: 60px;
82
- background-color: #d32f2f;
83
- border-radius: 50%;
84
- transition: all 0.2s ease;
85
- }
86
- .recording .record-icon {
87
- width: 40px;
88
- height: 40px;
89
- border-radius: 10%;
90
- }
91
- </style>
92
-
93
- <script>
94
- let isRecording = false;
95
- let mediaRecorder;
96
- let audioChunks = [];
97
- let recordingInterval;
98
- let count_voice = 0;
99
- let before_rate = 0;
100
-
101
- // メンバーとチャートの初期化
102
- let members = [];
103
- let voiceData = [];
104
- let baseMemberColors = ["#4caf50", "#007bff", "#ffc107", "#dc3545", "#28a745", "#9c27b0", "#ff9800"];
105
-
106
- // Chart.js の初期化
107
- const ctx = document.getElementById("speechChart").getContext("2d");
108
- const speechChart = new Chart(ctx, {
109
- type: "doughnut",
110
- data: {
111
- labels: members,
112
- datasets: [
113
- {
114
- data: voiceData,
115
- backgroundColor: getMemberColors(members.length),
116
- },
117
- ],
118
- },
119
- options: {
120
- responsive: true,
121
- plugins: {
122
- legend: {
123
- display: true,
124
- position: "bottom",
125
- labels: { color: "white" },
126
- },
127
- },
128
- },
129
- });
130
-
131
- // サーバーからメンバー情報を取得してチャートを更新する関数
132
- async function updateChartFrom() {
133
- try {
134
- const response = await fetch("/confirm");
135
- if (!response.ok) {
136
- throw new Error(`HTTP error! status: ${response.status}`);
137
- }
138
- const data = await response.json();
139
-
140
- if (!data || !data.members || !Array.isArray(data.members)) {
141
- console.error("Invalid member data received:", data);
142
- members = ["member1"];
143
- voiceData = [50, 50];
144
- updateChart();
145
- return;
146
- }
147
-
148
- members = data.members;
149
- voiceData = [];
150
- for (let i = 0; i < members.length; i++) {
151
- voiceData.push(100 / members.length);
152
- }
153
-
154
- updateChart();
155
- } catch (error) {
156
- console.error("Failed to fetch member data:", error);
157
- members = ["member1"];
158
- voiceData = [50, 50];
159
- updateChart();
160
- }
161
- }
162
-
163
- function updateChart() {
164
- // 一人モードの場合は、ユーザーとグレー(無音)の比率をチャートに表示
165
- if (members.length === 1) {
166
- const userName = members[0];
167
- speechChart.data.labels = [userName, "無音"];
168
- speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
169
- } else {
170
- // 複数メンバーの場合は通常通りの処理
171
- speechChart.data.labels = members;
172
- speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length);
173
- }
174
-
175
- speechChart.data.datasets[0].data = voiceData;
176
- speechChart.update();
177
- }
178
-
179
- // ページ読み込み時にチャート情報を更新
180
- updateChartFrom();
181
-
182
- // 録音ボタンの録音開始/停止処理
183
- async function toggleRecording() {
184
- const recordButton = document.getElementById("recordButton");
185
-
186
- if (!isRecording) {
187
- // 録音開始
188
- isRecording = true;
189
- recordButton.classList.add("recording");
190
- try {
191
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
192
- mediaRecorder = new MediaRecorder(stream);
193
- audioChunks = [];
194
-
195
- mediaRecorder.ondataavailable = (event) => {
196
- if (event.data.size > 0) {
197
- audioChunks.push(event.data);
198
- }
199
- };
200
-
201
- mediaRecorder.onstop = () => {
202
- sendAudioChunks([...audioChunks]);
203
- audioChunks = [];
204
- };
205
-
206
- mediaRecorder.start();
207
- } catch (error) {
208
- console.error("マイクへのアクセスに失敗しました:", error);
209
- isRecording = false;
210
- recordButton.classList.remove("recording");
211
- }
212
- } else {
213
- // 録音停止
214
- isRecording = false;
215
- recordButton.classList.remove("recording");
216
- if (mediaRecorder && mediaRecorder.state === "recording") {
217
- mediaRecorder.stop();
218
- count_voice = 0;
219
- before_rate = 0;
220
- }
221
- }
222
- }
223
-
224
- function sendAudioChunks(chunks) {
225
- const audioBlob = new Blob(chunks, { type: "audio/wav" });
226
- const reader = new FileReader();
227
- reader.onloadend = () => {
228
- const base64String = reader.result.split(",")[1];
229
- const form = document.getElementById("recordForm");
230
- const nameInput = form.querySelector('input[name="name"]');
231
- const name = nameInput ? nameInput.value : "unknown";
232
- fetch("/upload_audio", {
233
- method: "POST",
234
- headers: { "Content-Type": "application/json" },
235
- body: JSON.stringify({ audio_data: base64String, name: name }),
236
- })
237
- .then((response) => response.json())
238
- .then((data) => {
239
- if (data.error) {
240
- alert("エラー: " + data.error);
241
- console.error(data.details);
242
- } else if (data.rate !== undefined) {
243
- updateChartData(data.rate);
244
- }
245
- })
246
- .catch((error) => {
247
- console.error("エラー:", error);
248
- });
249
- };
250
- reader.readAsDataURL(audioBlob);
251
- }
252
-
253
- function getMemberColors(memberCount) {
254
- // 一人モードの場合は特別な処理をしない(updateChartで処理するため)
255
- if (memberCount <= 1) {
256
- return ["#4caf50", "#757575"];
257
- } else {
258
- let colors = [];
259
- for (let i = 0; i < memberCount; i++) {
260
- colors.push(baseMemberColors[i % baseMemberColors.length]);
261
- }
262
- return colors;
263
- }
264
- }
265
-
266
- function updateChartData(newRate) {
267
- // 一人モードの時の処理
268
- if (members.length === 1) {
269
- if (count_voice === 0) {
270
- speechChart.data.datasets[0].data = [newRate, 100 - newRate];
271
- before_rate = newRate;
272
- } else if (count_voice === 1) {
273
- let tmp_rate = (newRate + before_rate) / 2;
274
- speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
275
- before_rate = tmp_rate;
276
- } else {
277
- let tmp_rate = (newRate + before_rate * 2) / 3;
278
- speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
279
- before_rate = tmp_rate;
280
- }
281
- count_voice++;
282
-
283
- // 一人モードでは常に緑色とグレーの組み合わせを使用
284
- speechChart.data.labels = [members[0], "無音"];
285
- speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
286
- } else {
287
- // 複数メンバーの場合は元の処理
288
- if (count_voice === 0) {
289
- speechChart.data.datasets[0].data = [newRate, 100 - newRate];
290
- before_rate = newRate;
291
- } else if (count_voice === 1) {
292
- let tmp_rate = (newRate + before_rate) / 2;
293
- speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
294
- before_rate = tmp_rate;
295
- } else {
296
- let tmp_rate = (newRate + before_rate * 2) / 3;
297
- speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
298
- before_rate = tmp_rate;
299
- }
300
- count_voice++;
301
- speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length);
302
- }
303
-
304
- speechChart.update();
305
- }
306
-
307
- function showTalkdetail() {
308
- window.location.href = "talk_detail";
309
- }
310
-
311
- function showResults() {
312
- window.location.href = "feedback";
313
- }
314
-
315
- function showUserRegister() {
316
- fetch("/reset");
317
- window.location.href = "userregister";
318
- }
319
- </script>
320
- </body>
321
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Voice Recorder Interface</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ </head>
10
+ <body class="flex flex-col items-center justify-center h-screen bg-gray-900 text-white">
11
+ <!-- メンバーを登録ボタン -->
12
+ <div class="flex items-center mb-5">
13
+ <button
14
+ id="registerButton"
15
+ onclick="showUserRegister()"
16
+ class="px-4 py-2 bg-blue-600 rounded-md hover:bg-blue-700 transition"
17
+ >
18
+ メンバーを登録
19
+ </button>
20
+ </div>
21
+
22
+ <!-- チャート表示部 -->
23
+ <div class="chart w-72 h-72 mb-5">
24
+ <canvas id="speechChart"></canvas>
25
+ </div>
26
+
27
+ <!-- 録音フォーム -->
28
+ <form
29
+ id="recordForm"
30
+ action="/submit"
31
+ method="POST"
32
+ class="flex items-center space-x-2 w-full sm:w-auto"
33
+ onsubmit="event.preventDefault();"
34
+ >
35
+
36
+ <!-- 録音ボタン -->
37
+ <button type="button" class="record-button" id="recordButton" onclick="toggleRecording()">
38
+ <div class="record-icon" id="recordIcon"></div>
39
+ </button>
40
+ </form>
41
+
42
+ <!-- 結果ボタン -->
43
+ <div class="flex mt-5">
44
+ <button
45
+ id="historyButton"
46
+ onclick="showTalkdetail()"
47
+ class="result-button px-4 py-2 mx-2 bg-green-600 rounded-md hover:bg-green-700 transition"
48
+ >
49
+ 会話履歴を表示
50
+ </button>
51
+ <button
52
+ id="feedbackButton"
53
+ onclick="showResults()"
54
+ class="result-button px-4 py-2 mx-2 bg-blue-600 rounded-md hover:bg-blue-700 transition"
55
+ >
56
+ フィードバック画面を表示
57
+ </button>
58
+ </div>
59
+
60
+ <style>
61
+ .record-button {
62
+ width: 80px;
63
+ height: 80px;
64
+ background-color: transparent;
65
+ border-radius: 50%;
66
+ border: 4px solid white;
67
+ display: flex;
68
+ justify-content: center;
69
+ align-items: center;
70
+ cursor: pointer;
71
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
72
+ transition: all 0.2s ease;
73
+ }
74
+ .record-icon {
75
+ width: 60px;
76
+ height: 60px;
77
+ background-color: #d32f2f;
78
+ border-radius: 50%;
79
+ transition: all 0.2s ease;
80
+ }
81
+ .recording .record-icon {
82
+ width: 40px;
83
+ height: 40px;
84
+ border-radius: 10%;
85
+ }
86
+ </style>
87
+
88
+ <script>
89
+ let isRecording = false;
90
+ let mediaRecorder;
91
+ let audioChunks = [];
92
+ let recordingInterval;
93
+ let count_voice = 0;
94
+ let before_rate = 0;
95
+
96
+ // メンバーとチャートの初期化
97
+ let members = [];
98
+ let voiceData = [];
99
+ let baseMemberColors = ["#4caf50", "#007bff", "#ffc107", "#dc3545", "#28a745", "#9c27b0", "#ff9800"];
100
+
101
+ // Chart.js の初期化
102
+ const ctx = document.getElementById("speechChart").getContext("2d");
103
+ const speechChart = new Chart(ctx, {
104
+ type: "doughnut",
105
+ data: {
106
+ labels: members,
107
+ datasets: [
108
+ {
109
+ data: voiceData,
110
+ backgroundColor: getMemberColors(members.length),
111
+ },
112
+ ],
113
+ },
114
+ options: {
115
+ responsive: true,
116
+ plugins: {
117
+ legend: {
118
+ display: true,
119
+ position: "bottom",
120
+ labels: { color: "white" },
121
+ },
122
+ },
123
+ },
124
+ });
125
+
126
+ // サーバーからメンバー情報を取得してチャートを更新する関数
127
+ async function updateChartFrom() {
128
+ try {
129
+ const response = await fetch("/confirm");
130
+ if (!response.ok) {
131
+ throw new Error(`HTTP error! status: ${response.status}`);
132
+ }
133
+ const data = await response.json();
134
+
135
+ if (!data || !data.members || !Array.isArray(data.members)) {
136
+ console.error("Invalid member data received:", data);
137
+ members = ["member1"];
138
+ voiceData = [50, 50];
139
+ updateChart();
140
+ return;
141
+ }
142
+
143
+ members = data.members;
144
+ voiceData = [];
145
+ for (let i = 0; i < members.length; i++) {
146
+ voiceData.push(100 / members.length);
147
+ }
148
+
149
+ updateChart();
150
+ } catch (error) {
151
+ console.error("Failed to fetch member data:", error);
152
+ members = ["member1"];
153
+ voiceData = [50, 50];
154
+ updateChart();
155
+ }
156
+ }
157
+
158
+ function updateChart() {
159
+ // 一人モードの場合は、ユーザーとグレー(無音)の比率をチャートに表示
160
+ if (members.length === 1) {
161
+ const userName = members[0];
162
+ speechChart.data.labels = [userName, "無音"];
163
+ speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
164
+ } else {
165
+ // 複数メンバーの場合は通常通りの処理
166
+ speechChart.data.labels = members;
167
+ speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length);
168
+ }
169
+
170
+ speechChart.data.datasets[0].data = voiceData;
171
+ speechChart.update();
172
+ }
173
+
174
+ // ページ読み込み時にチャート情報を更新
175
+ updateChartFrom();
176
+
177
+ // 録音ボタンの録音開始/停止処理
178
+ async function toggleRecording() {
179
+ const recordButton = document.getElementById("recordButton");
180
+
181
+ if (!isRecording) {
182
+ // 録音開始
183
+ isRecording = true;
184
+ recordButton.classList.add("recording");
185
+ try {
186
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
187
+ mediaRecorder = new MediaRecorder(stream);
188
+ audioChunks = [];
189
+
190
+ mediaRecorder.ondataavailable = (event) => {
191
+ if (event.data.size > 0) {
192
+ audioChunks.push(event.data);
193
+ }
194
+ };
195
+
196
+ mediaRecorder.onstop = () => {
197
+ sendAudioChunks([...audioChunks]);
198
+ audioChunks = [];
199
+ };
200
+
201
+ mediaRecorder.start();
202
+ } catch (error) {
203
+ console.error("マイクへのアクセスに失敗しました:", error);
204
+ isRecording = false;
205
+ recordButton.classList.remove("recording");
206
+ }
207
+ } else {
208
+ // 録音停止
209
+ isRecording = false;
210
+ recordButton.classList.remove("recording");
211
+ if (mediaRecorder && mediaRecorder.state === "recording") {
212
+ mediaRecorder.stop();
213
+ count_voice = 0;
214
+ before_rate = 0;
215
+ }
216
+ }
217
+ }
218
+
219
+ function sendAudioChunks(chunks) {
220
+ const audioBlob = new Blob(chunks, { type: "audio/wav" });
221
+ const reader = new FileReader();
222
+ reader.onloadend = () => {
223
+ const base64String = reader.result.split(",")[1];
224
+ const form = document.getElementById("recordForm");
225
+ const nameInput = form.querySelector('input[name="name"]');
226
+ const name = nameInput ? nameInput.value : "unknown";
227
+ fetch("/upload_audio", {
228
+ method: "POST",
229
+ headers: { "Content-Type": "application/json" },
230
+ body: JSON.stringify({ audio_data: base64String, name: name }),
231
+ })
232
+ .then((response) => response.json())
233
+ .then((data) => {
234
+ if (data.error) {
235
+ alert("エラー: " + data.error);
236
+ console.error(data.details);
237
+ } else if (data.rate !== undefined) {
238
+ updateChartData(data.rate);
239
+ }
240
+ })
241
+ .catch((error) => {
242
+ console.error("エラー:", error);
243
+ });
244
+ };
245
+ reader.readAsDataURL(audioBlob);
246
+ }
247
+
248
+ function getMemberColors(memberCount) {
249
+ // 一人モードの場合は特別な処理をしない(updateChartで処理するため)
250
+ if (memberCount <= 1) {
251
+ return ["#4caf50", "#757575"];
252
+ } else {
253
+ let colors = [];
254
+ for (let i = 0; i < memberCount; i++) {
255
+ colors.push(baseMemberColors[i % baseMemberColors.length]);
256
+ }
257
+ return colors;
258
+ }
259
+ }
260
+
261
+ function updateChartData(newRate) {
262
+ // 一人モードの時の処理
263
+ if (members.length === 1) {
264
+ if (count_voice === 0) {
265
+ speechChart.data.datasets[0].data = [newRate, 100 - newRate];
266
+ before_rate = newRate;
267
+ } else if (count_voice === 1) {
268
+ let tmp_rate = (newRate + before_rate) / 2;
269
+ speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
270
+ before_rate = tmp_rate;
271
+ } else {
272
+ let tmp_rate = (newRate + before_rate * 2) / 3;
273
+ speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
274
+ before_rate = tmp_rate;
275
+ }
276
+ count_voice++;
277
+
278
+ // 一人モードでは常に緑色とグレーの組み合わせを使用
279
+ speechChart.data.labels = [members[0], "無音"];
280
+ speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
281
+ } else {
282
+ // 複数メンバーの場合は元の処理
283
+ if (count_voice === 0) {
284
+ speechChart.data.datasets[0].data = [newRate, 100 - newRate];
285
+ before_rate = newRate;
286
+ } else if (count_voice === 1) {
287
+ let tmp_rate = (newRate + before_rate) / 2;
288
+ speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
289
+ before_rate = tmp_rate;
290
+ } else {
291
+ let tmp_rate = (newRate + before_rate * 2) / 3;
292
+ speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
293
+ before_rate = tmp_rate;
294
+ }
295
+ count_voice++;
296
+ speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length);
297
+ }
298
+
299
+ speechChart.update();
300
+ }
301
+
302
+ function showTalkdetail() {
303
+ window.location.href = "talk_detail";
304
+ }
305
+
306
+ function showResults() {
307
+ window.location.href = "feedback";
308
+ }
309
+
310
+ function showUserRegister() {
311
+ fetch("/reset");
312
+ window.location.href = "userregister";
313
+ }
314
+ </script>
315
+ </body>
 
 
 
 
 
316
  </html>