Spaces:
Sleeping
Sleeping
<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> |