gogo2/web/chat-client.html
Dobromir Popov 364df3d891 wip
2024-06-10 17:32:00 +03:00

349 lines
15 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>Real-time Voice Chat</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css">
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-8">
<h1 class="text-2xl font-bold mb-4 text-center">Real-time Voice Chat</h1>
<div class="flex justify-center items-center mb-4">
<!-- Username Input -->
<input type="text" id="username" class="border rounded p-2 mr-4" placeholder="Enter your username">
<button id="btn-join" onclick="joinChat()"
class="hidden bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Join Chat</button>
<!-- Clear Session Option -->
<button id="btn-disconnect" onclick="clearSession()"
class="hidden bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">Clear Session</button>
</div>
<!-- Active Users List -->
<div id="active-users-container" class="hidden flex justify-center items-center mb-4">
<div class="w-1/3">
<h2 class="text-xl font-bold mb-2">Active Users</h2>
<select id="users-list" class="w-full bg-white p-4 rounded shadow" multiple size="10">
<!-- Dynamic list of users -->
</select>
<button onclick="startChat()"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded mt-4">Start
Chat</button>
</div>
</div>
<div id="previous-chats-container" class="hidden w-2/3 mx-auto">
<h2 class="text-xl font-bold mb-2">Previous Chats</h2>
<div id="previous-chats" class="bg-white p-4 rounded shadow">
<!-- Previous chats content -->
</div>
</div>
<!-- Chat Room -->
<div id="chat-room-container" class="hidden w-2/3 mx-auto">
<h2 class="text-xl font-bold mb-2">Chat Room</h2>
<div id="chat-room" class="bg-white p-4 rounded shadow mb-4">
<!-- Chat room content -->
<div class="mb-4">
<label class="flex items-center space-x-2">
<input type="checkbox" id="autosend" class="mr-2">
<span>Continuous</span>
</label>
</div>
<div class="mb-4">
<button id="record-button" disabled
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-4">Push to
Talk</button>
</div>
<div id="transcription" class="border rounded p-4 h-48 overflow-y-scroll mb-4">
<!-- Transcription content -->
</div>
<canvas id="canvas" class="w-full mb-4"></canvas>
<div class="flex justify-between items-center">
<button id="copyButton"
class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium py-2 px-4 rounded focus:outline-none"
onclick="copyToClipboard('transcription')">Copy</button>
<button id="clearButton"
class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium py-2 px-4 rounded focus:outline-none"
onclick="clearTranscription()">Clear</button>
</div>
</div>
</div>
<!-- Connection Status and Info -->
<div class="flex justify-center items-center mb-4">
<div id="connection-status" class="mr-4"></div>
</div>
<div class="flex justify-center items-center mb-4">
<div id="info"></div>
</div>
<div id="status-recording" class="flex justify-center items-center mb-4"></div>
</div>
<script type="module">
import { startListening, stopListening, toggleListening, InitAudioAnalyser, sendAudioToServerPost, sendAudioToServer, initializeVolumeChecker, serverTime, setSocket, setRecordButton } from './audio.js';
let socket;
let sessionId;
let username;
let users = [];
let selectedUsers = [];
let chats = [];
let recordButton;
let connectionStatus;
let statusRecording;
let connected = false;
document.getElementById('autosend').addEventListener('change', (event) => {
const autosend = event.target.checked;
fetch('/settings', {
method: 'POST',
body: JSON.stringify({ autosend, sessionId }),
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin'
});
});
function connect() {
return new Promise((resolve, reject) => {
connectionStatus.innerHTML = "Connecting to WS...";
let wsurl = "ws://localhost:8081";
fetch("/wsurl")
.then((response) => response.text())
.then((data) => {
wsurl = data;
console.log("Got ws url: '" + wsurl + "'");
})
.then(() => {
socket = new WebSocket(wsurl);
socket.onopen = () => {
connectionStatus.innerHTML = "Connected to " + wsurl;
recordButton.disabled = false;
connected = true;
resolve();
};
socket.onmessage = onmessage;
socket.onclose = () => {
connectionStatus.innerHTML = "Disconnected";
recordButton.disabled = true;
connected = false;
setTimeout(() => {
connect().then(resolve).catch(reject);
}, 5000);
};
setSocket(socket);
})
.catch((error) => {
connectionStatus.innerHTML = "Error getting ws url: " + error;
reject(error);
});
});
};
function onmessage(event) {
try {
let json = JSON.parse(event.data);
switch (json.type) {
case "sessionId":
sessionId = json.sessionId;
console.log("Got session id: " + sessionId);
break;
case "languageDetected":
statusRecording.innerHTML = "Detected language: " + json.languageDetected;
break;
case "text":
transcription.innerHTML += "\r\n" + json.text;
break;
case "userList":
users = json.users;
updateUserList();
break;
case "chats":
chats = json.chats;
updateChatList();
break;
case "newChatRoom":
chats.push(json.newChatRoom);
updateChatList();
break;
default:
console.log("Unknown message type:", json.type);
}
} catch (e) {
console.error("Failed to parse message", e);
}
let latency = Date.now() - serverTime;
if (autosend.checked) {
const arr = event.data.split(/[(\)]/);
let queue = arr[1];
let text = arr[2].trim();
info.innerHTML = "latency: " + latency + "ms; server queue: " + queue + " requests";
transcription.value += text + " ";
statusRecording.innerHTML = "Listening...";
statusRecording.style.color = "black";
} else {
transcription.innerHTML = event.data;
}
}
function joinChat() {
username = document.getElementById('username').value;
if (username.trim() === "") {
alert("Please enter a username");
return;
}
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("WebSocket connection is not open. Please wait and try again.");
return;
}
socket.send(JSON.stringify({ type: 'join', username }));
document.cookie = `sessionId=${sessionId}; path=/;`;
document.cookie = `username=${username}; path=/;`;
showClearSessionOption();
}
function clearSession() {
document.cookie = "sessionId=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
location.reload();
}
function showClearSessionOption() {
const sessionId = getCookie("sessionId");
if (sessionId) {
if (!socket || socket.readyState !== WebSocket.OPEN) {
connect().then(() => {
socket.send(JSON.stringify({ type: 'reconnect', sessionId }));
//initializeVolumeChecker();
});
}
document.getElementById('btn-disconnect').classList.remove('hidden');
document.getElementById('btn-join').classList.add('hidden');
document.getElementById('active-users-container').classList.remove('hidden');
document.getElementById('previous-chats-container').classList.remove('hidden');
} else {
document.getElementById('btn-disconnect').classList.add('hidden');
document.getElementById('btn-join').classList.remove('hidden');
}
}
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
window.onload = () => {
recordButton = document.getElementById("record-button");
setRecordButton(recordButton);
connectionStatus = document.getElementById("connection-status");
statusRecording = document.getElementById("status-recording");
showClearSessionOption();
connect().then(() => {
initializeVolumeChecker();
});
};
function copyToClipboard(id) {
var textarea = document.getElementById(id);
textarea.select();
document.execCommand('copy');
}
function clearTranscription() {
document.getElementById('transcription').innerText = '';
}
function updateUserList() {
const usersList = document.getElementById('users-list');
usersList.innerHTML = '';
users.forEach(user => {
const option = document.createElement('option');
option.value = user.sessionId;
option.innerText = user.username;
if (user.username === username) {
option.innerText += " (me)";
}
usersList.appendChild(option);
});
}
function startChat() {
const selectedOptions = Array.from(document.querySelectorAll('#users-list option:checked'));
selectedUsers = selectedOptions.map(option => option.value);
if (selectedUsers.length === 0) {
alert("Please select at least one user to start a chat.");
return;
}
selectedUsers.push(sessionId); // Add self to the selected users list for self-chat
socket.send(JSON.stringify({ type: 'startChat', users: selectedUsers }));
document.getElementById('chat-room-container').classList.remove('hidden');
displayChatParticipants(selectedUsers);
}
function displayChatParticipants(participants) {
const chatRoom = document.getElementById('chat-room');
let participantsHtml = '<div class="flex flex-wrap mb-4">';
participants.forEach(participantId => {
const user = users.find(u => u.sessionId === participantId);
const status = user ? "online" : "offline";
const username = user ? user.username : "Unknown User";
participantsHtml += `<span class="inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium bg-${status === 'online' ? 'green' : 'gray'}-100 text-${status === 'online' ? 'green' : 'gray'}-800 mr-2 mb-2">
${username} (${status})
</span>`;
});
participantsHtml += '</div>';
chatRoom.insertAdjacentHTML('afterbegin', participantsHtml);
}
function fetchPreviousChats(username) {
fetch(`/chats?username=${username}`)
.then(response => response.json())
.then(data => {
chats = data.chats;
updateChatList();
});
}
function sendSocketMessage(message) {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(message));
}
}
function updateChatList() {
const previousChats = document.getElementById('previous-chats');
previousChats.innerHTML = '';
chats.forEach(chat => {
const chatDiv = document.createElement('div');
chatDiv.classList.add('border', 'rounded', 'p-2', 'mb-2');
const participants = chat.participants.join(', ');
const status = chat.participants.map(participant => {
const user = users.find(u => u.username === participant);
return user ? `${participant} (online)` : `${participant} (offline)`;
}).join(', ');
chatDiv.innerHTML = `<strong>Participants:</strong> ${participants}<br><strong>Status:</strong> ${status}`;
previousChats.appendChild(chatDiv);
});
}
// Expose functions to global scope
window.joinChat = joinChat;
window.clearSession = clearSession;
window.copyToClipboard = copyToClipboard;
window.clearTranscription = clearTranscription;
window.startChat = startChat;
</script>
<script src="https://cdn.webrtc-experiment.com/MediaStreamRecorder.js"></script>
</body>
</html>