rein0421 commited on
Commit
4ea224b
·
verified ·
1 Parent(s): d45f43f

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +320 -0
index.html ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ <style>
8
+ body {
9
+ display: flex;
10
+ flex-direction: column;
11
+ justify-content: center;
12
+ align-items: center;
13
+ height: 100vh;
14
+ margin: 0;
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;
76
+ width: 80px;
77
+ height: 80px;
78
+ background-color: transparent;
79
+ border-radius: 50%;
80
+ border: 4px solid white;
81
+ display: flex;
82
+ justify-content: center;
83
+ align-items: center;
84
+ cursor: pointer;
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;
91
+ background-color: #d32f2f;
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;
109
+ border: none;
110
+ border-radius: 5px;
111
+ color: white;
112
+ cursor: pointer;
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
+ }
119
+ </style>
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
+ }
201
+ };
202
+
203
+ mediaRecorder.onstop = () => {
204
+ sendAudioChunks([...audioChunks]);
205
+ audioChunks = [];
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;
246
+ before_rate = 0;
247
+ }
248
+ }
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>
320
+ </html>