rein0421 commited on
Commit
4df27e3
·
verified ·
1 Parent(s): 4663c23

Upload index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +161 -422
templates/index.html CHANGED
@@ -1,422 +1,161 @@
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
- <link
10
- rel="stylesheet"
11
- href="https://use.fontawesome.com/releases/v5.10.0/css/all.css"
12
- />
13
- </head>
14
- <body
15
- class="flex items-center justify-center h-screen bg-gray-900 text-white"
16
- onclick="closeMenu(event)"
17
- >
18
- <!-- 全体を囲む枠 -->
19
- <div
20
- class="border-4 border-gray-400 p-8 rounded-lg w-full sm:w-auto relative"
21
- >
22
- <!-- タイトル -->
23
- <div class="text-center text-4xl font-bold mb-5">JustTalk</div>
24
-
25
- <!-- ハンバーガーメニュー -->
26
- <div class="absolute top-4 right-4">
27
- <button
28
- id="menuButton"
29
- class="text-white text-2xl focus:outline-none"
30
- onclick="toggleMenu(event)"
31
- >
32
- <i class="fas fa-bars"></i>
33
- </button>
34
-
35
- <!-- メニューの本体 -->
36
- <div
37
- id="menu"
38
- class="absolute top-0 right-0 h-full w-64 bg-gray-800 text-white transform translate-x-full transition-transform duration-300 ease-in-out opacity-0 visibility-hidden"
39
- >
40
- <div class="px-4 py-2 text-lg font-semibold">メニュー</div>
41
- <button
42
- onclick="showUserRegister()"
43
- class="block px-4 py-2 text-sm hover:bg-gray-700"
44
- >
45
- メンバーを追加
46
- </button>
47
- <button
48
- onclick="showResults()"
49
- class="block px-4 py-2 text-sm hover:bg-gray-700"
50
- >
51
- フィードバックを表示
52
- </button>
53
- <button
54
- onclick="showTalkdetail()"
55
- class="block px-4 py-2 text-sm hover:bg-gray-700"
56
- >
57
- 会話詳細を表示
58
- </button>
59
- <button
60
- onclick="resetAction()"
61
- class="block px-4 py-2 text-sm hover:bg-gray-700"
62
- >
63
- リセット
64
- </button>
65
- <button
66
- onclick="toggleMenu(event)"
67
- class="block px-4 py-2 text-sm hover:bg-gray-700"
68
- >
69
- 閉じる
70
- </button>
71
- </div>
72
- </div>
73
-
74
- <!-- チャート表示部 -->
75
- <div class="chart w-72 h-72 mb-5">
76
- <canvas id="speechChart"></canvas>
77
- </div>
78
-
79
- <!-- 録音フォーム -->
80
- <form
81
- id="recordForm"
82
- action="/submit"
83
- method="POST"
84
- class="flex items-center justify-center space-x-2 w-full sm:w-auto"
85
- onsubmit="event.preventDefault();"
86
- >
87
- <!-- 録音ボタン -->
88
- <button
89
- type="button"
90
- class="record-button"
91
- id="recordButton"
92
- onclick="toggleRecording()"
93
- >
94
- <div class="record-icon" id="recordIcon"></div>
95
- </button>
96
- </form>
97
- </div>
98
-
99
- <style>
100
- .record-button {
101
- width: 80px;
102
- height: 80px;
103
- background-color: transparent;
104
- border-radius: 50%;
105
- border: 4px solid white;
106
- display: flex;
107
- justify-content: center;
108
- align-items: center;
109
- cursor: pointer;
110
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
111
- transition: all 0.2s ease;
112
- }
113
- .record-icon {
114
- width: 60px;
115
- height: 60px;
116
- background-color: #d32f2f;
117
- border-radius: 50%;
118
- transition: all 0.2s ease;
119
- }
120
- .recording .record-icon {
121
- width: 40px;
122
- height: 40px;
123
- border-radius: 10%;
124
- }
125
- .icon i {
126
- font-size: 20px;
127
- }
128
-
129
- /* ハンバーガーメニュー */
130
- #menu {
131
- position: absolute;
132
- top: 0;
133
- right: 0;
134
- z-index: 10;
135
- transform: translateX(100%); /* 初期状態で画面外 */
136
- visibility: hidden; /* 非表示 */
137
- opacity: 0; /* 非表示 */
138
- background-color: rgba(31, 41, 55, 1); /* 不透明な背景色 */
139
- transition: transform 0.3s ease-in-out, visibility 0s 0.3s,
140
- opacity 0.3s ease-in-out; /* スライド後に表示 */
141
- }
142
-
143
- #menu.open {
144
- transform: translateX(0); /* メニューが開く */
145
- visibility: visible; /* 表示 */
146
- opacity: 1; /* 完全に表示 */
147
- transition: transform 0.3s ease-in-out, visibility 0s 0s,
148
- opacity 0.3s ease-in-out; /* すぐに表示 */
149
- }
150
-
151
- /* レスポンシブデザイン */
152
- @media (max-width: 640px) {
153
- .w-72 {
154
- width: 90%;
155
- }
156
- .h-72 {
157
- height: 300px;
158
- }
159
- }
160
- </style>
161
-
162
- <script>
163
- let isRecording = false;
164
- let mediaRecorder;
165
- let audioChunks = [];
166
- let recordingInterval;
167
- let count_voice = 0;
168
- let before_rate = [];
169
- const RECORDING_INTERVAL_MS = 1000; // 5秒
170
-
171
- // メンバーとチャートの初期化
172
- let members = [];
173
- let voiceData = [];
174
- let baseMemberColors = ["#4caf50", "#007bff", "#ffc107", "#dc3545", "#28a745", "#9c27b0", "#ff9800"];
175
-
176
- // Chart.js の初期化
177
- const ctx = document.getElementById("speechChart").getContext("2d");
178
- const speechChart = new Chart(ctx, {
179
- type: "doughnut",
180
- data: {
181
- labels: members,
182
- datasets: [
183
- {
184
- data: voiceData,
185
- backgroundColor: getMemberColors(members.length),
186
- },
187
- ],
188
- },
189
- options: {
190
- responsive: true,
191
- plugins: {
192
- legend: {
193
- display: true,
194
- position: "bottom",
195
- labels: { color: "white" },
196
- },
197
- },
198
- },
199
- });
200
-
201
- // サーバーからメンバー情報を取得してチャートを更新する関数
202
- async function updateChartFrom() {
203
- try {
204
- const response = await fetch("/confirm");
205
- if (!response.ok) {
206
- throw new Error(`HTTP error! status: ${response.status}`);
207
- }
208
- const data = await response.json();
209
-
210
- if (!data || !data.members || !Array.isArray(data.members)) {
211
- console.error("Invalid member data received:", data);
212
- members = ["member1"];
213
- voiceData = [50, 50];
214
- updateChart();
215
- return;
216
- }
217
-
218
- members = data.members;
219
- voiceData = [];
220
- for (let i = 0; i < members.length; i++) {
221
- voiceData.push(100 / members.length);
222
- }
223
-
224
- updateChart();
225
- } catch (error) {
226
- console.error("Failed to fetch member data:", error);
227
- members = ["member1"];
228
- voiceData = [50, 50];
229
- updateChart();
230
- }
231
- }
232
-
233
- function updateChart() {
234
- // 一人モードの場合は、ユーザーとグレー(無音)の比率をチャートに表示
235
- if (members.length === 1) {
236
- const userName = members[0];
237
- speechChart.data.labels = [userName, "無音"];
238
- speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
239
- } else {
240
- // 複数メンバーの場合は通常通りの処理
241
- speechChart.data.labels = members;
242
- speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length);
243
- }
244
-
245
- speechChart.data.datasets[0].data = voiceData;
246
- speechChart.update();
247
- }
248
-
249
- // ページ読み込み時にチャート情報を更新
250
- updateChartFrom();
251
-
252
- // 録音ボタンの録音開始/停止処理
253
- async function toggleRecording() {
254
- const recordButton = document.getElementById("recordButton");
255
-
256
- if (!isRecording) {
257
- // 録音開始
258
- isRecording = true;
259
- recordButton.classList.add("recording");
260
- try {
261
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
262
- mediaRecorder = new MediaRecorder(stream);
263
- audioChunks = [];
264
-
265
- mediaRecorder.ondataavailable = (event) => {
266
- if (event.data.size > 0) {
267
- audioChunks.push(event.data);
268
- }
269
- };
270
-
271
- mediaRecorder.onstop = () => {
272
- sendAudioChunks([...audioChunks]);
273
- audioChunks = [];
274
- };
275
-
276
- mediaRecorder.start();
277
- // 5秒ごとに録音を停止して送信するインターバルを設定
278
- recordingInterval = setInterval(() => {
279
- if (mediaRecorder && mediaRecorder.state === "recording") {
280
- mediaRecorder.stop();
281
- mediaRecorder.start();
282
- }
283
- }, RECORDING_INTERVAL_MS);
284
- } catch (error) {
285
- console.error("マイクへのアクセスに失敗しました:", error);
286
- isRecording = false;
287
- recordButton.classList.remove("recording");
288
- }
289
- } else {
290
- // 録音停止
291
- isRecording = false;
292
- recordButton.classList.remove("recording");
293
- if (mediaRecorder && mediaRecorder.state === "recording") {
294
- mediaRecorder.stop();
295
- }
296
- clearInterval(recordingInterval);
297
- count_voice = 0;
298
- before_rate = [];
299
- }
300
- }
301
-
302
- function sendAudioChunks(chunks) {
303
- const audioBlob = new Blob(chunks, { type: "audio/wav" });
304
- const reader = new FileReader();
305
- reader.onloadend = () => {
306
- const base64String = reader.result.split(",")[1];
307
- const form = document.getElementById("recordForm");
308
- const nameInput = form.querySelector('input[name="name"]');
309
- const name = nameInput ? nameInput.value : "unknown";
310
- fetch("/upload_audio", {
311
- method: "POST",
312
- headers: { "Content-Type": "application/json" },
313
- body: JSON.stringify({ audio_data: base64String, name: name }),
314
- })
315
- .then((response) => response.json())
316
- .then((data) => {
317
- if (data.error) {
318
- alert("エラー: " + data.error);
319
- console.error(data.details);
320
- } else if (data.rate !== undefined) {
321
- updateChartData(data.rate);
322
- } else if (data.rates !== undefined) {
323
- updateChartData(data.rates);
324
- }
325
- })
326
- .catch((error) => {
327
- console.error("エラー:", error);
328
- });
329
- };
330
- reader.readAsDataURL(audioBlob);
331
- }
332
-
333
- function getMemberColors(memberCount) {
334
- // 一人モードの場合は特別な処理をしない(updateChartで処理するため)
335
- if (memberCount <= 1) {
336
- return ["#4caf50", "#757575"];
337
- } else {
338
- let colors = [];
339
- for (let i = 0; i < memberCount; i++) {
340
- colors.push(baseMemberColors[i % baseMemberColors.length]);
341
- }
342
- return colors;
343
- }
344
- }
345
-
346
- function updateChartData(newRate) {
347
- // 一人モードの時の処理
348
- if (members.length === 1) {
349
- if (count_voice === 0) {
350
- speechChart.data.datasets[0].data = [newRate, 100 - newRate];
351
- before_rate = [newRate];
352
- } else {
353
- // 一人モードでは、過去のデータと現在のデータを加重平均する
354
- let tmp_rate = (newRate + before_rate[0] * count_voice) / (count_voice + 1);
355
- speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
356
- before_rate = [tmp_rate];
357
- }
358
- count_voice++;
359
-
360
- // 一人モードでは常に緑色とグレーの組み合わせを使用
361
- speechChart.data.labels = [members[0], "無音"];
362
- speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
363
- } else {
364
- console.log(before_rate)
365
- // 複数人モードの処理
366
- if (!Array.isArray(newRate)) {
367
- console.error("newRate is not an array:", newRate);
368
- return;
369
- }
370
- if (newRate.length !== members.length) {
371
- console.error(
372
- "newRate length does not match members length:",
373
- newRate,
374
- members
375
- );
376
- return;
377
- }
378
-
379
- let averagedRates = new Array(newRate.length);
380
-
381
- for (let i = 0; i < newRate.length; i++) {
382
- let tmp_rate;
383
-
384
- if (count_voice === 0) {
385
- // 初回はそのまま
386
- tmp_rate = newRate[i];
387
- } else {
388
- // 2回目以降は、過去の平均値と現在の値を加重平均する
389
- tmp_rate = (newRate[i] + before_rate[i] * count_voice) / (count_voice + 1);
390
- }
391
- averagedRates[i] = tmp_rate;
392
- }
393
-
394
- // before_rateを更新
395
- before_rate = averagedRates;
396
-
397
- //グラフに反映
398
- speechChart.data.datasets[0].data = averagedRates;
399
- count_voice++;
400
- speechChart.data.datasets[0].backgroundColor = getMemberColors(
401
- members.length
402
- );
403
- }
404
-
405
- speechChart.update();
406
- }
407
-
408
- function showTalkdetail() {
409
- window.location.href = "talk_detail";
410
- }
411
-
412
- function showResults() {
413
- window.location.href = "feedback";
414
- }
415
-
416
- function showUserRegister() {
417
- fetch("/reset");
418
- window.location.href = "userregister";
419
- }
420
- </script>
421
- </body>
422
- </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
+ <link
10
+ rel="stylesheet"
11
+ href="https://use.fontawesome.com/releases/v5.10.0/css/all.css"
12
+ />
13
+ </head>
14
+ <body
15
+ class="flex items-center justify-center h-screen bg-gray-900 text-white"
16
+ onclick="closeMenu(event)"
17
+ >
18
+ <!-- 全体を囲む枠 -->
19
+ <div
20
+ class="border-4 border-gray-400 p-8 rounded-lg w-full sm:w-auto relative"
21
+ >
22
+ <!-- タイトル -->
23
+ <div class="text-center text-4xl font-bold mb-5">JustTalk</div>
24
+
25
+ <!-- ハンバーガーメニュー -->
26
+ <div class="absolute top-4 right-4">
27
+ <button
28
+ id="menuButton"
29
+ class="text-white text-2xl focus:outline-none"
30
+ onclick="toggleMenu(event)"
31
+ >
32
+ <i class="fas fa-bars"></i>
33
+ </button>
34
+
35
+ <!-- メニューの本体 -->
36
+ <div
37
+ id="menu"
38
+ class="absolute top-0 right-0 h-full w-64 bg-gray-800 text-white transform translate-x-full transition-transform duration-300 ease-in-out opacity-0 visibility-hidden"
39
+ >
40
+ <div class="px-4 py-2 text-lg font-semibold">メニュー</div>
41
+ <button
42
+ onclick="showUserRegister()"
43
+ class="block px-4 py-2 text-sm hover:bg-gray-700"
44
+ >
45
+ メンバーを追加
46
+ </button>
47
+ <button
48
+ onclick="showResults()"
49
+ class="block px-4 py-2 text-sm hover:bg-gray-700"
50
+ >
51
+ フィードバックを表示
52
+ </button>
53
+ <button
54
+ onclick="showTalkdetail()"
55
+ class="block px-4 py-2 text-sm hover:bg-gray-700"
56
+ >
57
+ 会話詳細を表示
58
+ </button>
59
+ <button
60
+ onclick="resetAction()"
61
+ class="block px-4 py-2 text-sm hover:bg-gray-700"
62
+ >
63
+ リセット
64
+ </button>
65
+ <button
66
+ onclick="toggleMenu(event)"
67
+ class="block px-4 py-2 text-sm hover:bg-gray-700"
68
+ >
69
+ 閉じる
70
+ </button>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- チャート表示部 -->
75
+ <div class="chart w-72 h-72 mb-5">
76
+ <canvas id="speechChart"></canvas>
77
+ </div>
78
+
79
+ <!-- 録音フォーム -->
80
+ <form
81
+ id="recordForm"
82
+ action="/submit"
83
+ method="POST"
84
+ class="flex items-center justify-center space-x-2 w-full sm:w-auto"
85
+ onsubmit="event.preventDefault();"
86
+ >
87
+ <!-- 録音ボタン -->
88
+ <button
89
+ type="button"
90
+ class="record-button"
91
+ id="recordButton"
92
+ onclick="toggleRecording()"
93
+ >
94
+ <div class="record-icon" id="recordIcon"></div>
95
+ </button>
96
+ </form>
97
+ </div>
98
+
99
+ <style>
100
+ .record-button {
101
+ width: 80px;
102
+ height: 80px;
103
+ background-color: transparent;
104
+ border-radius: 50%;
105
+ border: 4px solid white;
106
+ display: flex;
107
+ justify-content: center;
108
+ align-items: center;
109
+ cursor: pointer;
110
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
111
+ transition: all 0.2s ease;
112
+ }
113
+ .record-icon {
114
+ width: 60px;
115
+ height: 60px;
116
+ background-color: #d32f2f;
117
+ border-radius: 50%;
118
+ transition: all 0.2s ease;
119
+ }
120
+ .recording .record-icon {
121
+ width: 40px;
122
+ height: 40px;
123
+ border-radius: 10%;
124
+ }
125
+ .icon i {
126
+ font-size: 20px;
127
+ }
128
+ /* ハンバーガーメニュー */
129
+ #menu {
130
+ position: absolute;
131
+ top: 0;
132
+ right: 0;
133
+ z-index: 10;
134
+ transform: translateX(100%); /* 初期状態で画面外 */
135
+ visibility: hidden; /* 非表示 */
136
+ opacity: 0; /* 非表示 */
137
+ background-color: rgba(31, 41, 55, 1); /* 不透明な背景色 */
138
+ transition: transform 0.3s ease-in-out, visibility 0s 0.3s,
139
+ opacity 0.3s ease-in-out; /* スライド後に表示 */
140
+ }
141
+ #menu.open {
142
+ transform: translateX(0); /* メニューが開く */
143
+ visibility: visible; /* 表示 */
144
+ opacity: 1; /* 完全に表示 */
145
+ transition: transform 0.3s ease-in-out, visibility 0s 0s,
146
+ opacity 0.3s ease-in-out; /* すぐに表示 */
147
+ }
148
+ /* レスポンシブデザイン */
149
+ @media (max-width: 640px) {
150
+ .w-72 {
151
+ width: 90%;
152
+ }
153
+ .h-72 {
154
+ height: 300px;
155
+ }
156
+ }
157
+ </style>
158
+ <script src="{{ url_for('static', filename='process.js') }}"></script>
159
+
160
+ </body>
161
+ </html>