Spaces:
Runtime error
Runtime error
| <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> |