rein0421 commited on
Commit
de16ffe
·
verified ·
1 Parent(s): 7f39f82

Upload index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +167 -110
templates/index.html CHANGED
@@ -15,13 +15,61 @@
15
  background-color: #121212;
16
  color: white;
17
  }
18
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  .chart {
20
  width: 300px;
21
  height: 300px;
22
- margin-bottom: 50px; /* 円グラフとボタンの間隔を狭く */
23
  }
24
-
25
  .record-button {
26
  position: fixed;
27
  bottom: 30px;
@@ -37,7 +85,6 @@
37
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
38
  transition: all 0.2s ease;
39
  }
40
-
41
  .record-icon {
42
  width: 60px;
43
  height: 60px;
@@ -45,19 +92,17 @@
45
  border-radius: 50%;
46
  transition: all 0.2s ease;
47
  }
48
-
49
  .recording .record-icon {
50
  width: 40px;
51
  height: 40px;
52
  border-radius: 10%;
53
  }
54
-
55
  .result-buttons {
56
- margin-top: 5px; /* ボタン間の距離を少し縮める */
57
  display: flex;
58
  gap: 10px;
59
  }
60
-
61
  .result-button {
62
  padding: 10px 20px;
63
  background-color: #4caf50;
@@ -68,7 +113,6 @@
68
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
69
  transition: background-color 0.2s ease;
70
  }
71
-
72
  .result-button:hover {
73
  background-color: #388e3c;
74
  }
@@ -76,70 +120,81 @@
76
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
77
  </head>
78
  <body>
 
 
 
 
 
 
 
 
 
 
79
  <div class="chart">
80
  <canvas id="speechChart"></canvas>
81
  </div>
82
 
 
83
  <button class="record-button" id="recordButton" onclick="toggleRecording()">
84
  <div class="record-icon" id="recordIcon"></div>
85
  </button>
86
 
 
87
  <div class="result-buttons">
88
- <button class="result-button" id="historyButton" onclick="showHistory()">
89
- 会話履歴を表示
90
- </button>
91
- <button class="result-button" id="feedbackButton" onclick="showResults()">
92
- フィードバック画面を表示
93
- </button>
94
  </div>
95
 
96
  <script>
97
  let isRecording = false;
98
  let mediaRecorder;
99
  let audioChunks = [];
100
- let recordingInterval;
 
101
  let count_voice = 0;
102
  let before_rate = 0;
 
103
  // Chart.js の初期化
104
- const ctx = document.getElementById("speechChart").getContext("2d");
105
  const speechChart = new Chart(ctx, {
106
- type: "doughnut",
107
  data: {
108
- labels: ["自分", "他の人"],
109
- datasets: [
110
- {
111
- data: [30, 70],
112
- backgroundColor: ["#4caf50", "#757575"],
113
- },
114
- ],
115
  },
116
  options: {
117
  responsive: true,
118
  plugins: {
119
  legend: {
120
  display: true,
121
- position: "bottom",
122
- labels: { color: "white" },
123
- },
124
- },
125
- },
126
  });
127
 
 
 
 
 
 
128
  async function toggleRecording() {
129
- const recordButton = document.getElementById("recordButton");
130
 
131
  if (!isRecording) {
132
  // 録音開始
133
  isRecording = true;
134
- recordButton.classList.add("recording");
135
  try {
136
- const stream = await navigator.mediaDevices.getUserMedia({
137
- audio: true,
138
- });
139
  mediaRecorder = new MediaRecorder(stream);
140
  audioChunks = [];
141
 
142
- mediaRecorder.ondataavailable = (event) => {
143
  if (event.data.size > 0) {
144
  audioChunks.push(event.data);
145
  }
@@ -151,22 +206,40 @@
151
  };
152
 
153
  mediaRecorder.start();
154
- // 10秒ごとに自動で停止して送信
155
- recordingInterval = setInterval(() => {
156
- if (mediaRecorder && mediaRecorder.state === "recording") {
157
- mediaRecorder.stop();
158
- }
159
- }, 10000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  } catch (error) {
161
- console.error("マイクへのアクセスに失敗しました:", error);
162
  isRecording = false;
163
- recordButton.classList.remove("recording");
164
  }
165
  } else {
166
  // 手動停止
167
  isRecording = false;
168
- recordButton.classList.remove("recording");
169
- clearInterval(recordingInterval);
 
 
 
 
170
  if (mediaRecorder && mediaRecorder.state === "recording") {
171
  mediaRecorder.stop();
172
  count_voice = 0;
@@ -176,87 +249,71 @@
176
  }
177
 
178
  function sendAudioChunks(chunks) {
179
- const audioBlob = new Blob(chunks, { type: "audio/wav" });
180
  const reader = new FileReader();
181
  reader.onloadend = () => {
182
- const base64String = reader.result.split(",")[1]; // Base64エンコードされた音声データ
183
- fetch("/upload_audio", {
184
- method: "POST",
 
 
185
  headers: {
186
- "Content-Type": "application/json",
187
  },
188
  body: JSON.stringify({ audio_data: base64String }),
189
  })
190
- .then((response) => response.json())
191
- .then((data) => {
192
- if (data.error) {
193
- alert("エラー: " + data.error);
194
- console.error(data.details);
195
- } else if (data.rate !== undefined) {
196
- // 解析結果が返ってきた場合はチャートを更新
197
-
198
- if (count_voice == 0) {
199
- speechChart.data.datasets[0].data = [
200
- data.rate,
201
- 100 - data.rate,
202
- ];
203
- before_rate = data.rate;
204
- } else if (count_voice == 1) {
205
- let tmp_rate = (data.rate + before_rate) / 2; //データ数が二つだから平均をとる
206
- speechChart.data.datasets[0].data = [
207
- tmp_rate,
208
- 100 - tmp_rate,
209
- ];
210
- console.log(before_rate, tmp_rate, 100 - tmp_rate);
211
- before_rate = tmp_rate;
212
- } else {
213
- let tmp_rate = (data.rate + before_rate * 2) / 3; //過去のやつに重みを付けて三つで考える。
214
- speechChart.data.datasets[0].data = [
215
- tmp_rate,
216
- 100 - tmp_rate,
217
- ];
218
- console.log(before_rate, tmp_rate, 100 - tmp_rate);
219
- before_rate = tmp_rate; //ここのrateを保存しておく
220
- }
221
- count_voice++;
222
-
223
- speechChart.update();
224
- //lert('音声の解析が完了しました。自分の音声: ' + data.rate.toFixed(2) + '%, 他の人: ' + (100 - data.rate).toFixed(2) + '%');
225
  } else {
226
- alert("音声がバックエンドに送信されました。");
227
- }
228
- // 録音が継続中であれば、再度録音を開始(自動録音の連続処理)
229
- if (
230
- isRecording &&
231
- mediaRecorder &&
232
- mediaRecorder.state === "inactive"
233
- ) {
234
- mediaRecorder.start();
235
  }
236
- })
237
- .catch((error) => {
238
- console.error("エラー:", error);
239
- if (
240
- isRecording &&
241
- mediaRecorder &&
242
- mediaRecorder.state === "inactive"
243
- ) {
244
- mediaRecorder.start();
 
245
  }
246
- });
 
 
 
 
 
 
 
 
 
 
 
247
  };
248
  reader.readAsDataURL(audioBlob);
249
  }
250
 
251
  function showHistory() {
252
- // 会話履歴表示の画面があれば、そのページへ遷移する例
253
- window.location.href = "history";
254
- //alert('会話履歴を表示する機能は未実装です。');
255
  }
256
 
257
  function showResults() {
258
- // フィードバック画面へ遷移
259
- window.location.href = "feedback";
260
  }
261
  </script>
262
  </body>
 
15
  background-color: #121212;
16
  color: white;
17
  }
18
+ /* トグルスイッチ(基準音声保存用) */
19
+ .toggle-container {
20
+ display: flex;
21
+ align-items: center;
22
+ margin-bottom: 20px;
23
+ }
24
+ .toggle-label {
25
+ margin-right: 10px;
26
+ }
27
+ .toggle-switch {
28
+ position: relative;
29
+ display: inline-block;
30
+ width: 50px;
31
+ height: 24px;
32
+ }
33
+ .toggle-switch input {
34
+ opacity: 0;
35
+ width: 0;
36
+ height: 0;
37
+ }
38
+ .slider {
39
+ position: absolute;
40
+ cursor: pointer;
41
+ top: 0;
42
+ left: 0;
43
+ right: 0;
44
+ bottom: 0;
45
+ background-color: #757575;
46
+ transition: 0.2s;
47
+ border-radius: 34px;
48
+ }
49
+ .slider::before {
50
+ content: "";
51
+ position: absolute;
52
+ height: 18px;
53
+ width: 18px;
54
+ left: 4px;
55
+ bottom: 3px;
56
+ background-color: white;
57
+ transition: 0.2s;
58
+ border-radius: 50%;
59
+ }
60
+ input:checked + .slider {
61
+ background-color: #4caf50;
62
+ }
63
+ input:checked + .slider::before {
64
+ transform: translateX(26px);
65
+ }
66
+ /* チャートのスタイル */
67
  .chart {
68
  width: 300px;
69
  height: 300px;
70
+ margin-bottom: 50px;
71
  }
72
+ /* 録音ボタンのスタイル */
73
  .record-button {
74
  position: fixed;
75
  bottom: 30px;
 
85
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
86
  transition: all 0.2s ease;
87
  }
 
88
  .record-icon {
89
  width: 60px;
90
  height: 60px;
 
92
  border-radius: 50%;
93
  transition: all 0.2s ease;
94
  }
 
95
  .recording .record-icon {
96
  width: 40px;
97
  height: 40px;
98
  border-radius: 10%;
99
  }
100
+ /* 結果ボタンのスタイル */
101
  .result-buttons {
102
+ margin-top: 5px;
103
  display: flex;
104
  gap: 10px;
105
  }
 
106
  .result-button {
107
  padding: 10px 20px;
108
  background-color: #4caf50;
 
113
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
114
  transition: background-color 0.2s ease;
115
  }
 
116
  .result-button:hover {
117
  background-color: #388e3c;
118
  }
 
120
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
121
  </head>
122
  <body>
123
+ <!-- トグルスイッチ:基準音声保存モード -->
124
+ <div class="toggle-container">
125
+ <span class="toggle-label">基準音声を保存</span>
126
+ <label class="toggle-switch">
127
+ <input type="checkbox" id="baseVoiceToggle" />
128
+ <span class="slider"></span>
129
+ </label>
130
+ </div>
131
+
132
+ <!-- チャート表示部 -->
133
  <div class="chart">
134
  <canvas id="speechChart"></canvas>
135
  </div>
136
 
137
+ <!-- 録音ボタン -->
138
  <button class="record-button" id="recordButton" onclick="toggleRecording()">
139
  <div class="record-icon" id="recordIcon"></div>
140
  </button>
141
 
142
+ <!-- 結果ボタン -->
143
  <div class="result-buttons">
144
+ <button class="result-button" id="historyButton" onclick="showHistory()">会話履歴を表示</button>
145
+ <button class="result-button" id="feedbackButton" onclick="showResults()">フィードバック画面を表示</button>
 
 
 
 
146
  </div>
147
 
148
  <script>
149
  let isRecording = false;
150
  let mediaRecorder;
151
  let audioChunks = [];
152
+ let recordingInterval; // 通常モードでの10秒周期用
153
+ let baseTimeout; // 基準音声モード用のタイマー
154
  let count_voice = 0;
155
  let before_rate = 0;
156
+
157
  // Chart.js の初期化
158
+ const ctx = document.getElementById('speechChart').getContext('2d');
159
  const speechChart = new Chart(ctx, {
160
+ type: 'doughnut',
161
  data: {
162
+ labels: ['自分', '他の人'],
163
+ datasets: [{
164
+ data: [30, 70],
165
+ backgroundColor: ['#4caf50', '#757575'],
166
+ }],
 
 
167
  },
168
  options: {
169
  responsive: true,
170
  plugins: {
171
  legend: {
172
  display: true,
173
+ position: 'bottom',
174
+ labels: { color: 'white' }
175
+ }
176
+ }
177
+ }
178
  });
179
 
180
+ // トグルの状態を取得する関数
181
+ function isBaseVoiceMode() {
182
+ return document.getElementById('baseVoiceToggle').checked;
183
+ }
184
+
185
  async function toggleRecording() {
186
+ const recordButton = document.getElementById('recordButton');
187
 
188
  if (!isRecording) {
189
  // 録音開始
190
  isRecording = true;
191
+ recordButton.classList.add('recording');
192
  try {
193
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
 
 
194
  mediaRecorder = new MediaRecorder(stream);
195
  audioChunks = [];
196
 
197
+ mediaRecorder.ondataavailable = event => {
198
  if (event.data.size > 0) {
199
  audioChunks.push(event.data);
200
  }
 
206
  };
207
 
208
  mediaRecorder.start();
209
+
210
+ if (isBaseVoiceMode()) {
211
+ // 基準音声モード:10秒後に自動停止するタイマーをセット
212
+ baseTimeout = setTimeout(() => {
213
+ if (mediaRecorder && mediaRecorder.state === "recording") {
214
+ mediaRecorder.stop();
215
+ // 10秒経過しても録音ボタンがONなら強制的に停止&トグルをオフにする
216
+ isRecording = false;
217
+ recordButton.classList.remove('recording');
218
+ document.getElementById('baseVoiceToggle').checked = false;
219
+ }
220
+ }, 10000);
221
+ } else {
222
+ // 通常モード:10秒ごとに自動停止して送信、継続録音する処理
223
+ recordingInterval = setInterval(() => {
224
+ if (mediaRecorder && mediaRecorder.state === "recording") {
225
+ mediaRecorder.stop();
226
+ }
227
+ }, 10000);
228
+ }
229
  } catch (error) {
230
+ console.error('マイクへのアクセスに失敗しました:', error);
231
  isRecording = false;
232
+ recordButton.classList.remove('recording');
233
  }
234
  } else {
235
  // 手動停止
236
  isRecording = false;
237
+ recordButton.classList.remove('recording');
238
+ if (isBaseVoiceMode()) {
239
+ clearTimeout(baseTimeout);
240
+ } else {
241
+ clearInterval(recordingInterval);
242
+ }
243
  if (mediaRecorder && mediaRecorder.state === "recording") {
244
  mediaRecorder.stop();
245
  count_voice = 0;
 
249
  }
250
 
251
  function sendAudioChunks(chunks) {
252
+ const audioBlob = new Blob(chunks, { type: 'audio/wav' });
253
  const reader = new FileReader();
254
  reader.onloadend = () => {
255
+ const base64String = reader.result.split(',')[1]; // Base64エンコードされた音声データ
256
+ // エンドポイントの選択:基準音声モードなら '/upload_base_audio'
257
+ const endpoint = isBaseVoiceMode() ? '/upload_base_audio' : '/upload_audio';
258
+ fetch(endpoint, {
259
+ method: 'POST',
260
  headers: {
261
+ 'Content-Type': 'application/json',
262
  },
263
  body: JSON.stringify({ audio_data: base64String }),
264
  })
265
+ .then(response => response.json())
266
+ .then(data => {
267
+ if (data.error) {
268
+ alert('エラー: ' + data.error);
269
+ console.error(data.details);
270
+ } else if (data.rate !== undefined && !isBaseVoiceMode()) {
271
+ // 通常モードの場合、解析結果をチャートに反映
272
+ if (count_voice === 0) {
273
+ speechChart.data.datasets[0].data = [data.rate, 100 - data.rate];
274
+ before_rate = data.rate;
275
+ } else if (count_voice === 1) {
276
+ let tmp_rate = (data.rate + before_rate) / 2;
277
+ speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
278
+ before_rate = tmp_rate;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  } else {
280
+ let tmp_rate = (data.rate + before_rate * 2) / 3;
281
+ speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
282
+ before_rate = tmp_rate;
 
 
 
 
 
 
283
  }
284
+ count_voice++;
285
+ speechChart.update();
286
+ } else {
287
+ // 基準音声モードまたは解析結果がない場合
288
+ if (isBaseVoiceMode()) {
289
+ //alert('基準音声が保存されました。');
290
+ // トグルをリセット
291
+ document.getElementById('baseVoiceToggle').checked = false;
292
+ } else {
293
+ //alert('音声がバックエンドに送信されました。');
294
  }
295
+ }
296
+ // 通常モードの場合、録音が継続中なら次の録音を開始(自動連続録音)
297
+ if (!isBaseVoiceMode() && isRecording && mediaRecorder && mediaRecorder.state === "inactive") {
298
+ mediaRecorder.start();
299
+ }
300
+ })
301
+ .catch(error => {
302
+ console.error('エラー:', error);
303
+ if (!isBaseVoiceMode() && isRecording && mediaRecorder && mediaRecorder.state === "inactive") {
304
+ mediaRecorder.start();
305
+ }
306
+ });
307
  };
308
  reader.readAsDataURL(audioBlob);
309
  }
310
 
311
  function showHistory() {
312
+ alert('会話履歴を表示する機能は未実装です。');
 
 
313
  }
314
 
315
  function showResults() {
316
+ window.location.href = 'feedback';
 
317
  }
318
  </script>
319
  </body>