rein0421 commited on
Commit
9d18ad4
·
verified ·
1 Parent(s): d3479c3

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +347 -347
templates/index.html CHANGED
@@ -1,347 +1,347 @@
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 = [];
95
- const RECORDING_INTERVAL_MS = 2000; // 5
96
-
97
- // メンバーとチャートの初期化
98
- let members = [];
99
- let voiceData = [];
100
- let baseMemberColors = ["#4caf50", "#007bff", "#ffc107", "#dc3545", "#28a745", "#9c27b0", "#ff9800"];
101
-
102
- // Chart.js の初期化
103
- const ctx = document.getElementById("speechChart").getContext("2d");
104
- const speechChart = new Chart(ctx, {
105
- type: "doughnut",
106
- data: {
107
- labels: members,
108
- datasets: [
109
- {
110
- data: voiceData,
111
- backgroundColor: getMemberColors(members.length),
112
- },
113
- ],
114
- },
115
- options: {
116
- responsive: true,
117
- plugins: {
118
- legend: {
119
- display: true,
120
- position: "bottom",
121
- labels: { color: "white" },
122
- },
123
- },
124
- },
125
- });
126
-
127
- // サーバーからメンバー情報を取得してチャートを更新する関数
128
- async function updateChartFrom() {
129
- try {
130
- const response = await fetch("/confirm");
131
- if (!response.ok) {
132
- throw new Error(`HTTP error! status: ${response.status}`);
133
- }
134
- const data = await response.json();
135
-
136
- if (!data || !data.members || !Array.isArray(data.members)) {
137
- console.error("Invalid member data received:", data);
138
- members = ["member1"];
139
- voiceData = [50, 50];
140
- updateChart();
141
- return;
142
- }
143
-
144
- members = data.members;
145
- voiceData = [];
146
- for (let i = 0; i < members.length; i++) {
147
- voiceData.push(100 / members.length);
148
- }
149
-
150
- updateChart();
151
- } catch (error) {
152
- console.error("Failed to fetch member data:", error);
153
- members = ["member1"];
154
- voiceData = [50, 50];
155
- updateChart();
156
- }
157
- }
158
-
159
- function updateChart() {
160
- // 一人モードの場合は、ユーザーとグレー(無音)の比率をチャートに表示
161
- if (members.length === 1) {
162
- const userName = members[0];
163
- speechChart.data.labels = [userName, "無音"];
164
- speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
165
- } else {
166
- // 複数メンバーの場合は通常通りの処理
167
- speechChart.data.labels = members;
168
- speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length);
169
- }
170
-
171
- speechChart.data.datasets[0].data = voiceData;
172
- speechChart.update();
173
- }
174
-
175
- // ページ読み込み時にチャート情報を更新
176
- updateChartFrom();
177
-
178
- // 録音ボタンの録音開始/停止処理
179
- async function toggleRecording() {
180
- const recordButton = document.getElementById("recordButton");
181
-
182
- if (!isRecording) {
183
- // 録音開始
184
- isRecording = true;
185
- recordButton.classList.add("recording");
186
- try {
187
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
188
- mediaRecorder = new MediaRecorder(stream);
189
- audioChunks = [];
190
-
191
- mediaRecorder.ondataavailable = (event) => {
192
- if (event.data.size > 0) {
193
- audioChunks.push(event.data);
194
- }
195
- };
196
-
197
- mediaRecorder.onstop = () => {
198
- sendAudioChunks([...audioChunks]);
199
- audioChunks = [];
200
- };
201
-
202
- mediaRecorder.start();
203
- // 5秒ごとに録音を停止して送信するインターバルを設定
204
- recordingInterval = setInterval(() => {
205
- if (mediaRecorder && mediaRecorder.state === "recording") {
206
- mediaRecorder.stop();
207
- mediaRecorder.start();
208
- }
209
- }, RECORDING_INTERVAL_MS);
210
- } catch (error) {
211
- console.error("マイクへのアクセスに失敗しました:", error);
212
- isRecording = false;
213
- recordButton.classList.remove("recording");
214
- }
215
- } else {
216
- // 録音停止
217
- isRecording = false;
218
- recordButton.classList.remove("recording");
219
- if (mediaRecorder && mediaRecorder.state === "recording") {
220
- mediaRecorder.stop();
221
- }
222
- clearInterval(recordingInterval);
223
- count_voice = 0;
224
- before_rate = [];
225
- }
226
- }
227
-
228
- function sendAudioChunks(chunks) {
229
- const audioBlob = new Blob(chunks, { type: "audio/wav" });
230
- const reader = new FileReader();
231
- reader.onloadend = () => {
232
- const base64String = reader.result.split(",")[1];
233
- const form = document.getElementById("recordForm");
234
- const nameInput = form.querySelector('input[name="name"]');
235
- const name = nameInput ? nameInput.value : "unknown";
236
- fetch("/upload_audio", {
237
- method: "POST",
238
- headers: { "Content-Type": "application/json" },
239
- body: JSON.stringify({ audio_data: base64String, name: name }),
240
- })
241
- .then((response) => response.json())
242
- .then((data) => {
243
- if (data.error) {
244
- alert("エラー: " + data.error);
245
- console.error(data.details);
246
- } else if (data.rate !== undefined) {
247
- updateChartData(data.rate);
248
- } else if (data.rates !== undefined) {
249
- updateChartData(data.rates);
250
- }
251
- })
252
- .catch((error) => {
253
- console.error("エラー:", error);
254
- });
255
- };
256
- reader.readAsDataURL(audioBlob);
257
- }
258
-
259
- function getMemberColors(memberCount) {
260
- // 一人モードの場合は特別な処理をしない(updateChartで処理するため)
261
- if (memberCount <= 1) {
262
- return ["#4caf50", "#757575"];
263
- } else {
264
- let colors = [];
265
- for (let i = 0; i < memberCount; i++) {
266
- colors.push(baseMemberColors[i % baseMemberColors.length]);
267
- }
268
- return colors;
269
- }
270
- }
271
-
272
- function updateChartData(newRate) {
273
- // 一人モードの時の処理
274
- if (members.length === 1) {
275
- if (count_voice === 0) {
276
- speechChart.data.datasets[0].data = [newRate, 100 - newRate];
277
- before_rate = [newRate];
278
- } else {
279
- // 一人モードでは、過去のデータと現在のデータを加重平均する
280
- let tmp_rate = (newRate + before_rate[0] * count_voice) / (count_voice + 1);
281
- speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
282
- before_rate = [tmp_rate];
283
- }
284
- count_voice++;
285
-
286
- // 一人モードでは常に緑色とグレーの組み合わせを使用
287
- speechChart.data.labels = [members[0], "無音"];
288
- speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
289
- } else {
290
- // 複数人モードの処理
291
- if (!Array.isArray(newRate)) {
292
- console.error("newRate is not an array:", newRate);
293
- return;
294
- }
295
- if (newRate.length !== members.length) {
296
- console.error(
297
- "newRate length does not match members length:",
298
- newRate,
299
- members
300
- );
301
- return;
302
- }
303
-
304
- let averagedRates = new Array(newRate.length);
305
-
306
- for (let i = 0; i < newRate.length; i++) {
307
- let tmp_rate;
308
-
309
- if (count_voice === 0) {
310
- // 初回はそのまま
311
- tmp_rate = newRate[i];
312
- } else {
313
- // 2回目以降は、過去の平均値と現在の値を加重平均する
314
- tmp_rate = (newRate[i] + before_rate[i] * count_voice) / (count_voice + 1);
315
- }
316
- averagedRates[i] = tmp_rate;
317
- }
318
-
319
- // before_rateを更新
320
- before_rate = averagedRates;
321
-
322
- //グラフに反映
323
- speechChart.data.datasets[0].data = averagedRates;
324
- count_voice++;
325
- speechChart.data.datasets[0].backgroundColor = getMemberColors(
326
- members.length
327
- );
328
- }
329
-
330
- speechChart.update();
331
- }
332
-
333
- function showTalkdetail() {
334
- window.location.href = "talk_detail";
335
- }
336
-
337
- function showResults() {
338
- window.location.href = "feedback";
339
- }
340
-
341
- function showUserRegister() {
342
- fetch("/reset");
343
- window.location.href = "userregister";
344
- }
345
- </script>
346
- </body>
347
- </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 = [];
95
+ const RECORDING_INTERVAL_MS = 1000; // 1
96
+
97
+ // メンバーとチャートの初期化
98
+ let members = [];
99
+ let voiceData = [];
100
+ let baseMemberColors = ["#4caf50", "#007bff", "#ffc107", "#dc3545", "#28a745", "#9c27b0", "#ff9800"];
101
+
102
+ // Chart.js の初期化
103
+ const ctx = document.getElementById("speechChart").getContext("2d");
104
+ const speechChart = new Chart(ctx, {
105
+ type: "doughnut",
106
+ data: {
107
+ labels: members,
108
+ datasets: [
109
+ {
110
+ data: voiceData,
111
+ backgroundColor: getMemberColors(members.length),
112
+ },
113
+ ],
114
+ },
115
+ options: {
116
+ responsive: true,
117
+ plugins: {
118
+ legend: {
119
+ display: true,
120
+ position: "bottom",
121
+ labels: { color: "white" },
122
+ },
123
+ },
124
+ },
125
+ });
126
+
127
+ // サーバーからメンバー情報を取得してチャートを更新する関数
128
+ async function updateChartFrom() {
129
+ try {
130
+ const response = await fetch("/confirm");
131
+ if (!response.ok) {
132
+ throw new Error(`HTTP error! status: ${response.status}`);
133
+ }
134
+ const data = await response.json();
135
+
136
+ if (!data || !data.members || !Array.isArray(data.members)) {
137
+ console.error("Invalid member data received:", data);
138
+ members = ["member1"];
139
+ voiceData = [50, 50];
140
+ updateChart();
141
+ return;
142
+ }
143
+
144
+ members = data.members;
145
+ voiceData = [];
146
+ for (let i = 0; i < members.length; i++) {
147
+ voiceData.push(100 / members.length);
148
+ }
149
+
150
+ updateChart();
151
+ } catch (error) {
152
+ console.error("Failed to fetch member data:", error);
153
+ members = ["member1"];
154
+ voiceData = [50, 50];
155
+ updateChart();
156
+ }
157
+ }
158
+
159
+ function updateChart() {
160
+ // 一人モードの場合は、ユーザーとグレー(無音)の比率をチャートに表示
161
+ if (members.length === 1) {
162
+ const userName = members[0];
163
+ speechChart.data.labels = [userName, "無音"];
164
+ speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
165
+ } else {
166
+ // 複数メンバーの場合は通常通りの処理
167
+ speechChart.data.labels = members;
168
+ speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length);
169
+ }
170
+
171
+ speechChart.data.datasets[0].data = voiceData;
172
+ speechChart.update();
173
+ }
174
+
175
+ // ページ読み込み時にチャート情報を更新
176
+ updateChartFrom();
177
+
178
+ // 録音ボタンの録音開始/停止処理
179
+ async function toggleRecording() {
180
+ const recordButton = document.getElementById("recordButton");
181
+
182
+ if (!isRecording) {
183
+ // 録音開始
184
+ isRecording = true;
185
+ recordButton.classList.add("recording");
186
+ try {
187
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
188
+ mediaRecorder = new MediaRecorder(stream);
189
+ audioChunks = [];
190
+
191
+ mediaRecorder.ondataavailable = (event) => {
192
+ if (event.data.size > 0) {
193
+ audioChunks.push(event.data);
194
+ }
195
+ };
196
+
197
+ mediaRecorder.onstop = () => {
198
+ sendAudioChunks([...audioChunks]);
199
+ audioChunks = [];
200
+ };
201
+
202
+ mediaRecorder.start();
203
+ // 5秒ごとに録音を停止して送信するインターバルを設定
204
+ recordingInterval = setInterval(() => {
205
+ if (mediaRecorder && mediaRecorder.state === "recording") {
206
+ mediaRecorder.stop();
207
+ mediaRecorder.start();
208
+ }
209
+ }, RECORDING_INTERVAL_MS);
210
+ } catch (error) {
211
+ console.error("マイクへのアクセスに失敗しました:", error);
212
+ isRecording = false;
213
+ recordButton.classList.remove("recording");
214
+ }
215
+ } else {
216
+ // 録音停止
217
+ isRecording = false;
218
+ recordButton.classList.remove("recording");
219
+ if (mediaRecorder && mediaRecorder.state === "recording") {
220
+ mediaRecorder.stop();
221
+ }
222
+ clearInterval(recordingInterval);
223
+ count_voice = 0;
224
+ before_rate = [];
225
+ }
226
+ }
227
+
228
+ function sendAudioChunks(chunks) {
229
+ const audioBlob = new Blob(chunks, { type: "audio/wav" });
230
+ const reader = new FileReader();
231
+ reader.onloadend = () => {
232
+ const base64String = reader.result.split(",")[1];
233
+ const form = document.getElementById("recordForm");
234
+ const nameInput = form.querySelector('input[name="name"]');
235
+ const name = nameInput ? nameInput.value : "unknown";
236
+ fetch("/upload_audio", {
237
+ method: "POST",
238
+ headers: { "Content-Type": "application/json" },
239
+ body: JSON.stringify({ audio_data: base64String, name: name }),
240
+ })
241
+ .then((response) => response.json())
242
+ .then((data) => {
243
+ if (data.error) {
244
+ alert("エラー: " + data.error);
245
+ console.error(data.details);
246
+ } else if (data.rate !== undefined) {
247
+ updateChartData(data.rate);
248
+ } else if (data.rates !== undefined) {
249
+ updateChartData(data.rates);
250
+ }
251
+ })
252
+ .catch((error) => {
253
+ console.error("エラー:", error);
254
+ });
255
+ };
256
+ reader.readAsDataURL(audioBlob);
257
+ }
258
+
259
+ function getMemberColors(memberCount) {
260
+ // 一人モードの場合は特別な処理をしない(updateChartで処理するため)
261
+ if (memberCount <= 1) {
262
+ return ["#4caf50", "#757575"];
263
+ } else {
264
+ let colors = [];
265
+ for (let i = 0; i < memberCount; i++) {
266
+ colors.push(baseMemberColors[i % baseMemberColors.length]);
267
+ }
268
+ return colors;
269
+ }
270
+ }
271
+
272
+ function updateChartData(newRate) {
273
+ // 一人モードの時の処理
274
+ if (members.length === 1) {
275
+ if (count_voice === 0) {
276
+ speechChart.data.datasets[0].data = [newRate, 100 - newRate];
277
+ before_rate = [newRate];
278
+ } else {
279
+ // 一人モードでは、過去のデータと現在のデータを加重平均する
280
+ let tmp_rate = (newRate + before_rate[0] * count_voice) / (count_voice + 1);
281
+ speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
282
+ before_rate = [tmp_rate];
283
+ }
284
+ count_voice++;
285
+
286
+ // 一人モードでは常に緑色とグレーの組み合わせを使用
287
+ speechChart.data.labels = [members[0], "無音"];
288
+ speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
289
+ } else {
290
+ // 複数人モードの処理
291
+ if (!Array.isArray(newRate)) {
292
+ console.error("newRate is not an array:", newRate);
293
+ return;
294
+ }
295
+ if (newRate.length !== members.length) {
296
+ console.error(
297
+ "newRate length does not match members length:",
298
+ newRate,
299
+ members
300
+ );
301
+ return;
302
+ }
303
+
304
+ let averagedRates = new Array(newRate.length);
305
+
306
+ for (let i = 0; i < newRate.length; i++) {
307
+ let tmp_rate;
308
+
309
+ if (count_voice === 0) {
310
+ // 初回はそのまま
311
+ tmp_rate = newRate[i];
312
+ } else {
313
+ // 2回目以降は、過去の平均値と現在の値を加重平均する
314
+ tmp_rate = (newRate[i] + before_rate[i] * count_voice) / (count_voice + 1);
315
+ }
316
+ averagedRates[i] = tmp_rate;
317
+ }
318
+
319
+ // before_rateを更新
320
+ before_rate = averagedRates;
321
+
322
+ //グラフに反映
323
+ speechChart.data.datasets[0].data = averagedRates;
324
+ count_voice++;
325
+ speechChart.data.datasets[0].backgroundColor = getMemberColors(
326
+ members.length
327
+ );
328
+ }
329
+
330
+ speechChart.update();
331
+ }
332
+
333
+ function showTalkdetail() {
334
+ window.location.href = "talk_detail";
335
+ }
336
+
337
+ function showResults() {
338
+ window.location.href = "feedback";
339
+ }
340
+
341
+ function showUserRegister() {
342
+ fetch("/reset");
343
+ window.location.href = "userregister";
344
+ }
345
+ </script>
346
+ </body>
347
+ </html>