354 lines
15 KiB
HTML
354 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Voice Chat Messenger</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
|
</head>
|
|
|
|
<body class="bg-gray-100">
|
|
<div class="container mx-auto px-4 py-8">
|
|
<h1 class="text-3xl font-bold mb-8 text-center text-blue-600">Voice Chat Messenger</h1>
|
|
|
|
<!-- Login/Register Form -->
|
|
<div id="auth-container" class="max-w-md mx-auto bg-white p-6 rounded-lg shadow-md mb-8">
|
|
<h2 class="text-2xl font-semibold mb-4">Login or Register</h2>
|
|
<input type="text" id="username" class="w-full border rounded p-2 mb-4" placeholder="Username">
|
|
<input type="password" id="password" class="w-full border rounded p-2 mb-4" placeholder="Password">
|
|
<div class="flex justify-between">
|
|
<button onclick="login()"
|
|
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Login</button>
|
|
<button onclick="register()"
|
|
class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">Register</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Chat Interface (initially hidden) -->
|
|
<div id="chat-interface" class="hidden">
|
|
<div class="flex">
|
|
<!-- Sidebar -->
|
|
<div class="w-1/4 bg-white rounded-lg shadow-md mr-4 p-4">
|
|
<h2 class="text-xl font-semibold mb-4">Chats</h2>
|
|
<div id="chat-list" class="space-y-2">
|
|
<!-- Chat list items will be inserted here -->
|
|
</div>
|
|
<button onclick="showNewChatModal()"
|
|
class="mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full">New
|
|
Chat</button>
|
|
</div>
|
|
|
|
<!-- Main Chat Area -->
|
|
<div class="w-3/4 bg-white rounded-lg shadow-md p-4">
|
|
<div id="current-chat-info" class="mb-4">
|
|
<h2 class="text-xl font-semibold">Current Chat</h2>
|
|
<div id="chat-participants" class="text-sm text-gray-600"></div>
|
|
</div>
|
|
<div id="messages" class="h-96 overflow-y-auto mb-4 p-2 border rounded">
|
|
<!-- Messages will be inserted here -->
|
|
</div>
|
|
<div class="flex items-center">
|
|
<button id="record-button"
|
|
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-2">Push to
|
|
Talk</button>
|
|
<div id="status-recording" class="text-sm"></div>
|
|
</div>
|
|
<div class="mt-2">
|
|
<label class="inline-flex items-center">
|
|
<input type="checkbox" id="autosend" class="form-checkbox">
|
|
<span class="ml-2">Continuous Mode</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- New Chat Modal (initially hidden) -->
|
|
<div id="new-chat-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full">
|
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
|
<h3 class="text-lg font-semibold mb-4">Start a New Chat</h3>
|
|
<select id="users-list" class="w-full p-2 border rounded mb-4" multiple>
|
|
<!-- User options will be inserted here -->
|
|
</select>
|
|
<div class="flex justify-end">
|
|
<button onclick="startNewChat()"
|
|
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-2">Start
|
|
Chat</button>
|
|
<button onclick="hideNewChatModal()"
|
|
class="bg-gray-300 hover:bg-gray-400 text-black font-bold py-2 px-4 rounded">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Connection Status -->
|
|
<div id="connection-status" class="fixed bottom-4 right-4 bg-gray-800 text-white p-2 rounded"></div>
|
|
</div>
|
|
<script>
|
|
// Declare these variables and functions in the global scope
|
|
var socket;
|
|
var sessionId;
|
|
var currentUser;
|
|
var users = [];
|
|
var chats = [];
|
|
var currentChatId;
|
|
|
|
// Make these functions global
|
|
window.login = function() {
|
|
const username = document.getElementById('username').value;
|
|
const password = document.getElementById('password').value;
|
|
fetch('/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.sessionId) {
|
|
sessionId = data.sessionId;
|
|
currentUser = { id: data.userId, username };
|
|
showChatInterface();
|
|
connect();
|
|
} else {
|
|
alert('Login failed. Please try again.');
|
|
}
|
|
});
|
|
};
|
|
|
|
window.register = function() {
|
|
const username = document.getElementById('username').value;
|
|
const password = document.getElementById('password').value;
|
|
fetch('/register', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.userId) {
|
|
alert('Registration successful. Please login.');
|
|
} else {
|
|
alert('Registration failed. Please try again.');
|
|
}
|
|
});
|
|
};
|
|
|
|
window.showNewChatModal = function() {
|
|
document.getElementById('new-chat-modal').classList.remove('hidden');
|
|
};
|
|
|
|
window.hideNewChatModal = function() {
|
|
document.getElementById('new-chat-modal').classList.add('hidden');
|
|
};
|
|
|
|
window.startNewChat = function() {
|
|
const selectedUsers = Array.from(document.getElementById('users-list').selectedOptions).map(option => option.value);
|
|
if (selectedUsers.length > 0) {
|
|
socket.send(JSON.stringify({ type: 'startChat', users: selectedUsers }));
|
|
hideNewChatModal();
|
|
} else {
|
|
alert('Please select at least one user to start a chat.');
|
|
}
|
|
};
|
|
|
|
// Connect to WebSocket
|
|
function connect() {
|
|
return new Promise((resolve, reject) => {
|
|
const connectionStatus = document.getElementById("connection-status");
|
|
connectionStatus.textContent = "Connecting...";
|
|
fetch("/wsurl")
|
|
.then(response => response.text())
|
|
.then(wsurl => {
|
|
socket = new WebSocket(wsurl);
|
|
socket.onopen = () => {
|
|
connectionStatus.textContent = "Connected";
|
|
resolve(socket);
|
|
};
|
|
socket.onmessage = handleMessage;
|
|
socket.onclose = () => {
|
|
connectionStatus.textContent = "Disconnected";
|
|
setTimeout(() => connect().then(resolve).catch(reject), 5000);
|
|
};
|
|
})
|
|
.catch(error => {
|
|
connectionStatus.textContent = "Connection error";
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
function handleMessage(event) {
|
|
const data = JSON.parse(event.data);
|
|
switch (data.type) {
|
|
case 'sessionId':
|
|
sessionId = data.sessionId;
|
|
break;
|
|
case 'userList':
|
|
users = data.users;
|
|
updateUserList();
|
|
break;
|
|
case 'chats':
|
|
chats = data.chats;
|
|
updateChatList();
|
|
break;
|
|
case 'chat':
|
|
displayChat(data.chat);
|
|
break;
|
|
case 'text':
|
|
case 'transcriptionResult':
|
|
addMessage(data.text);
|
|
break;
|
|
case 'audio':
|
|
playAudio(data.audio);
|
|
break;
|
|
}
|
|
}
|
|
function showChatInterface() {
|
|
document.getElementById('auth-container').classList.add('hidden');
|
|
document.getElementById('chat-interface').classList.remove('hidden');
|
|
}
|
|
function updateUserList() {
|
|
const usersList = document.getElementById('users-list');
|
|
usersList.innerHTML = '';
|
|
users.forEach(user => {
|
|
if (user.id !== currentUser.id) {
|
|
const option = document.createElement('option');
|
|
option.value = user.id;
|
|
option.textContent = user.username;
|
|
usersList.appendChild(option);
|
|
}
|
|
});
|
|
}
|
|
function updateChatList() {
|
|
const chatList = document.getElementById('chat-list');
|
|
chatList.innerHTML = '';
|
|
chats.forEach(chat => {
|
|
const chatItem = document.createElement('div');
|
|
chatItem.className = 'p-2 hover:bg-gray-100 cursor-pointer';
|
|
chatItem.textContent = chat.participants.map(p => p.username).join(', ');
|
|
chatItem.onclick = () => selectChat(chat.id);
|
|
chatList.appendChild(chatItem);
|
|
});
|
|
}
|
|
function selectChat(chatId) {
|
|
currentChatId = chatId;
|
|
socket.send(JSON.stringify({ type: 'enterChat', chatId }));
|
|
}
|
|
function displayChat(chat) {
|
|
const messagesContainer = document.getElementById('messages');
|
|
messagesContainer.innerHTML = '';
|
|
chat.messages.forEach(addMessage);
|
|
const participantsContainer = document.getElementById('chat-participants');
|
|
participantsContainer.textContent = Participants: ${ chat.participants.map(p => p.username).join(', ') };
|
|
}
|
|
function addMessage(message) {
|
|
const messagesContainer = document.getElementById('messages');
|
|
const messageElement = document.createElement('div');
|
|
messageElement.className = 'mb-2';
|
|
messageElement.textContent = ${ message.sender }: ${ message.text };
|
|
messagesContainer.appendChild(messageElement);
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
}
|
|
function playAudio(audioBase64) {
|
|
const audio = new Audio(data: audio / mp3; base64, ${ audioBase64 });
|
|
audio.play();
|
|
}
|
|
// Audio recording logic
|
|
let mediaRecorder;
|
|
let audioChunks = [];
|
|
document.getElementById('record-button').addEventListener('mousedown', startRecording);
|
|
document.getElementById('record-button').addEventListener('mouseup', stopRecording);
|
|
document.getElementById('record-button').addEventListener('mouseleave', stopRecording);
|
|
function startRecording() {
|
|
navigator.mediaDevices.getUserMedia({ audio: true })
|
|
.then(stream => {
|
|
mediaRecorder = new MediaRecorder(stream);
|
|
|
|
mediaRecorder.ondataavailable = event => {
|
|
audioChunks.push(event.data);
|
|
};
|
|
mediaRecorder.onstop = sendAudioToServer;
|
|
mediaRecorder.start();
|
|
document.getElementById('status-recording').textContent = 'Recording...';
|
|
});
|
|
}
|
|
|
|
function stopRecording() {
|
|
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
|
|
mediaRecorder.stop();
|
|
document.getElementById('status-recording').textContent = 'Processing...';
|
|
}
|
|
}
|
|
|
|
function sendAudioToServer() {
|
|
const audioBlob = new Blob(audioChunks, { type: 'audio/ogg; codecs=opus' });
|
|
audioChunks = [];
|
|
|
|
const reader = new FileReader();
|
|
reader.onloadend = () => {
|
|
const base64Audio = reader.result.split(',')[1];
|
|
socket.send(JSON.stringify({
|
|
type: 'audio',
|
|
chatId: currentChatId,
|
|
audio: base64Audio
|
|
}));
|
|
};
|
|
reader.readAsDataURL(audioBlob);
|
|
}
|
|
|
|
// Initialize the app
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const storedSessionId = localStorage.getItem('sessionId');
|
|
if (storedSessionId) {
|
|
sessionId = storedSessionId;
|
|
showChatInterface();
|
|
connect();
|
|
}
|
|
});
|
|
|
|
// Continuous mode toggle
|
|
document.getElementById('autosend').addEventListener('change', (event) => {
|
|
const autosend = event.target.checked;
|
|
if (autosend) {
|
|
startContinuousRecording();
|
|
} else {
|
|
stopContinuousRecording();
|
|
}
|
|
});
|
|
|
|
let continuousRecorder;
|
|
|
|
function startContinuousRecording() {
|
|
navigator.mediaDevices.getUserMedia({ audio: true })
|
|
.then(stream => {
|
|
continuousRecorder = new MediaRecorder(stream);
|
|
continuousRecorder.ondataavailable = event => {
|
|
sendAudioToServer();
|
|
};
|
|
continuousRecorder.start(1000); // Send audio every second
|
|
document.getElementById('status-recording').textContent = 'Continuous mode active';
|
|
});
|
|
}
|
|
|
|
function stopContinuousRecording() {
|
|
if (continuousRecorder && continuousRecorder.state !== 'inactive') {
|
|
continuousRecorder.stop();
|
|
document.getElementById('status-recording').textContent = '';
|
|
}
|
|
}
|
|
|
|
// Logout function
|
|
function logout() {
|
|
localStorage.removeItem('sessionId');
|
|
location.reload();
|
|
}
|
|
|
|
// Add logout button to the UI
|
|
const logoutButton = document.createElement('button');
|
|
logoutButton.textContent = 'Logout';
|
|
logoutButton.className = 'bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded mt-4';
|
|
logoutButton.onclick = logout;
|
|
document.querySelector('.container').appendChild(logoutButton);
|
|
</script>
|
|
</body>
|
|
|
|
</html> |