JusTalk / templates /userRegister.html
rein0421's picture
Update templates/userRegister.html
3d39f0b verified
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>ユーザー音声登録</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://use.fontawesome.com/releases/v5.10.0/js/all.js"></script>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='menu.css') }}">
<style>
@keyframes pulse-scale {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.animate-pulse-scale {
animation: pulse-scale 1s infinite;
}
/* Record Button Styles */
.record-button {
width: 50px;
height: 50px;
background-color: transparent;
border-radius: 50%;
border: 2px solid white;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
transition: all 0.3s ease;
}
.record-icon {
width: 35px;
height: 35px;
background-color: #d32f2f;
border-radius: 50%;
transition: all 0.3s ease;
}
.record-button.recording .record-icon {
background-color: #f44336; /* 録音中は赤色 */
border-radius: 4px; /* 録音時に赤い部分だけ四角にする */
}
.recording .record-icon {
width: 20px;
height: 20px;
border-radius: 50%;
}
/* Title */
.main-title {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 1.5rem;
color: #fff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
/* Buttons */
.action-button {
margin-top: 1rem;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.2s ease;
width: 100%;
}
.action-button:hover {
background-color: rgba(55, 65, 81, 0.7);
}
.back-button {
background-color: #607d8b; /* 落ち着いたグレー */
color: white;
}
.add-button {
background-color: #4caf50; /* 落ち着いた緑色 */
color: white;
}
/* Disabled State */
.disabled {
opacity: 0.5;
pointer-events: none;
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #2d3748;
color: white;
padding: 2rem;
border-radius: 1rem;
width: 90%;
max-width: 500px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
}
.input-field {
width: 100%;
padding: 0.75rem;
border-radius: 0.5rem;
background-color: #1a202c;
border: 1px solid #4a5568;
color: white;
margin-bottom: 1rem;
}
.modal-buttons {
display: flex;
justify-content: space-between;
margin-top: 1rem;
}
.modal-button {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
cursor: pointer;
min-width: 100px;
text-align: center;
}
.record-modal-button {
background-color: #d32f2f;
color: white;
}
.cancel-button {
background-color: #64748b;
color: white;
}
</style>
</head>
<body>
<!-- Main Content Wrapper -->
<div class="main-content relative">
<!-- Title -->
<div class="main-title">JustTalk</div>
<!-- User List -->
<div id="people-list" class="space-y-4"></div>
<!-- Add Button -->
<button id="add-btn" class="action-button add-button">
<i class="fas fa-user-plus"></i> メンバーを追加
</button>
<!-- 録音画面へ移動ボタン(Back Buttonから変更) -->
<button
id="backButton"
onclick="showUserSelect()"
class="action-button back-button"
>
<i class="fas fa-users"></i> メンバー選択画面を表示
</button>
</div>
<!-- Modal for Adding New User -->
<div id="add-modal" class="modal">
<div class="modal-content">
<h2 class="text-xl font-bold mb-4">新しいメンバーを追加</h2>
<input id="user-name" type="text" placeholder="名前を入力" class="input-field">
<div class="flex justify-center my-4">
<div id="record-button" class="record-button">
<div class="record-icon"></div>
</div>
</div>
<div id="recording-status" class="text-center mb-4">録音をクリックして開始</div>
<div class="modal-buttons">
<button id="cancel-button" class="modal-button cancel-button">キャンセル</button>
<button id="save-button" class="modal-button record-modal-button" disabled>保存</button>
</div>
</div>
</div>
<script>
// グローバル変数
let mediaRecorder;
let audioChunks = [];
let registeredUsers = [];
let isRecording = false;
// ページ読み込み時に実行
document.addEventListener('DOMContentLoaded', () => {
loadUsers();
setupEventListeners();
});
// イベントリスナーの設定
function setupEventListeners() {
// 追加ボタン
document.getElementById('add-btn').addEventListener('click', () => {
openModal();
});
// 録音ボタン
document.getElementById('record-button').addEventListener('click', toggleRecording);
// キャンセルボタン
document.getElementById('cancel-button').addEventListener('click', closeModal);
// 保存ボタン
document.getElementById('save-button').addEventListener('click', saveRecording);
}
// ユーザーリストを読み込む
async function loadUsers() {
try {
const response = await fetch('/list_base_audio');
const data = await response.json();
if (data.status === "success" && Array.isArray(data.id)) {
registeredUsers = data.id;
displayUsers(registeredUsers);
} else {
console.error('Unexpected response format:', data);
}
} catch (error) {
console.error('Error loading users:', error);
}
}
// ユーザーリストを表示
function displayUsers(users) {
const peopleList = document.getElementById('people-list');
peopleList.innerHTML = '';
if (users.length === 0) {
peopleList.innerHTML = '<p class="text-gray-400 text-center">登録されたメンバーはいません</p>';
return;
}
users.forEach(user => {
const userDiv = document.createElement('div');
userDiv.className = 'bg-gray-700 rounded-lg p-4 flex justify-between items-center';
userDiv.innerHTML = `
<div class="flex items-center">
<i class="fas fa-user-circle text-2xl mr-3"></i>
<span>${user}</span>
</div>
<button class="delete-btn text-red-500 hover:text-red-300" data-name="${user}">
<i class="fas fa-trash"></i>
</button>
`;
peopleList.appendChild(userDiv);
});
// 削除ボタンのイベントリスナーを追加
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', function() {
const name = this.getAttribute('data-name');
deleteUser(name);
});
});
}
// ユーザーを削除
async function deleteUser(name) {
if (!confirm(`${name}を削除してもよろしいですか?`)) return;
try {
const response = await fetch('/reset_member', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
names: [name]
})
});
const data = await response.json();
if (data.status === "success") {
// 削除成功時、ユーザーリストを更新
loadUsers();
} else {
console.error('Error deleting user:', data.message);
alert('ユーザーの削除に失敗しました');
}
} catch (error) {
console.error('Error:', error);
alert('エラーが発生しました');
}
}
// モーダルを開く
function openModal() {
document.getElementById('add-modal').style.display = 'flex';
document.getElementById('user-name').value = '';
document.getElementById('save-button').disabled = true;
document.getElementById('recording-status').textContent = '録音をクリックして開始';
// 録音状態をリセット
isRecording = false;
audioChunks = [];
const recordButton = document.getElementById('record-button');
recordButton.classList.remove('recording');
}
// モーダルを閉じる
function closeModal() {
document.getElementById('add-modal').style.display = 'none';
// 録音中なら停止
if (mediaRecorder && isRecording) {
mediaRecorder.stop();
isRecording = false;
}
}
// 録音の開始/停止を切り替え
async function toggleRecording() {
const recordButton = document.getElementById('record-button');
const statusText = document.getElementById('recording-status');
if (!isRecording) {
// 録音開始
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.ondataavailable = event => {
audioChunks.push(event.data);
};
mediaRecorder.onstop = () => {
document.getElementById('save-button').disabled = false;
statusText.textContent = '録音完了!';
recordButton.classList.remove('recording');
};
mediaRecorder.start();
isRecording = true;
statusText.textContent = '録音中...';
recordButton.classList.add('recording');
} catch (error) {
console.error('録音の開始に失敗しました:', error);
statusText.textContent = '録音の開始に失敗しました';
}
} else {
// 録音停止
mediaRecorder.stop();
isRecording = false;
}
}
// 録音を保存
async function saveRecording() {
const userName = document.getElementById('user-name').value.trim();
if (!userName) {
alert('名前を入力してください');
return;
}
if (registeredUsers.includes(userName)) {
if (!confirm(`${userName}は既に登録されています。上書きしますか?`)) {
return;
}
}
if (audioChunks.length === 0) {
alert('録音データがありません');
return;
}
try {
// 録音データをBlobに変換
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
// Base64に変換
const reader = new FileReader();
reader.readAsDataURL(audioBlob);
reader.onloadend = async () => {
// Base64文字列から先頭の "data:audio/wav;base64," を削除
const base64Audio = reader.result.split(',')[1];
// サーバーに送信
const response = await fetch('/upload_base_audio', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: userName,
audio_data: base64Audio
})
});
const data = await response.json();
if (data.state === "Registration Success!") {
closeModal();
loadUsers(); // ユーザーリストを更新
alert(`${userName}を登録しました!`);
} else {
console.error('Error saving recording:', data);
alert('録音の保存に失敗しました');
}
};
} catch (error) {
console.error('Error:', error);
alert('エラーが発生しました');
}
}
// ユーザー選択画面に戻る
function showUserSelect() {
window.location.href = "/userselect";
}
</script>
</body>
</html>