sync up mobile and desktop drop downs and user lists!

modified:   templates/base.html
	modified:   templates/chat.html
This commit is contained in:
Russell Ballestrini 2024-11-24 17:35:54 -05:00
parent b01311a825
commit 8b90bc6485
2 changed files with 171 additions and 56 deletions

View file

@ -262,7 +262,7 @@
</style>
</head>
<body>
<a href="https://github.com/russellballestrini/opencompletion#interacting-with-language-models" target="_blank">🚀 docs for language models & other commands, also try /help</a>
<a href="https://github.com/russellballestrini/opencompletion#interacting-with-language-models" target="_blank">🚀 docs for models & other commands, also try /help</a>
<!-- Search form -->
<div id="search-form" style="width: 90%;">
<form action="/search" method="get">
@ -274,15 +274,62 @@
<!-- Hamburger button for mobile -->
<button id="hamburger-button"></button>
<!-- Modal for room list -->
<div id="room-list-modal">
<div id="room-list-modal-content">
<button id="close-modal-button" onclick="closeModal()">×</button>
<div id="rooms-list-modal-content">
<!-- Room list will be cloned here for mobile view -->
</div>
</div>
</div>
<!-- Modal for room list and utility belt -->
<div id="room-list-modal">
<div id="room-list-modal-content">
<button id="close-modal-button" onclick="closeModal()">×</button>
<div id="utility-belt-mobile">
<div id="model-chooser-mobile">
<label for="model-select-mobile">Choose Model:</label>
<select id="model-select-mobile">
<option value="None">None</option>
<option value="gemini-flash">gemini-flash</option>
<option value="gemini-flash-8b">gemini-flash-8b</option>
<option value="gemini-pro">gemini-pro</option>
<option value="grok-beta">grok-beta</option>
<option value="vllm/hermes-llama-3">vllm/hermes-llama-3</option>
<option value="gpt-4">gpt-4</option>
<option value="gpt-4o-2024-08-06">gpt-4o-2024-08-06</option>
<option value="gpt-mini">gpt-mini</option>
<option value="gpt-o1-mini">gpt-o1-mini</option>
<option value="claude-haiku">claude-haiku</option>
<option value="claude-sonnet">claude-sonnet</option>
<option value="claude-opus">claude-opus</option>
</select>
</div>
<div id="voice-chooser-mobile">
<label for="voice-select-mobile">Choose Voice:</label>
<select id="voice-select-mobile">
<option value="onyx">Onyx</option>
<option value="alloy">Alloy</option>
<option value="echo">Echo</option>
<option value="fable">Fable</option>
<option value="nova">Nova</option>
<option value="shimmer">Shimmer</option>
</select>
</div>
<div id="user-lists-mobile">
<div id="active-users-list">
<h3>Active Users</h3>
<ul id="active-users-mobile">
<!-- Active users will be dynamically populated here -->
</ul>
</div>
<div id="inactive-users-list">
<h3>Inactive Users</h3>
<ul id="inactive-users-mobile">
<!-- Inactive users will be dynamically populated here -->
</ul>
</div>
</div>
</div>
<div id="rooms-list-modal-content">
<!-- Room list will be cloned here for mobile view -->
</div>
</div>
</div>
<!-- Chatroom list -->
<div class="main-container">
@ -293,7 +340,13 @@
<a href="{{ url_for('chat', room_name=room.name) }}?{{ request.query_string.decode('utf-8')|safe }}">
<li data-room-id="{{ room.id }}">
<!-- Display the room title if available, otherwise the room name -->
<b>{{ room.name }}</b> {% if room.title %}<br />{{ room.title }} {% endif %}
<b>{{ room.name }}</b>
{% if room.title %}
<br />{{ room.title }}
{% endif %}
{% if room.get_active_users()|length %}
<br /> {{ room.get_active_users()|length }} users
{% endif %}
</li>
</a>
{% endfor %}

View file

@ -98,8 +98,48 @@ let userHasScrolledUp = false;
let currentAudio = null; // To keep track of the currently playing audio
let audioCache = {}; // Cache to store audio blobs
// Flag to prevent mutual updates on desktop/mobile
let isSyncingDropdowns = false;
// Function to sanitize the username
function sanitizeUsername(username) {
// Split the username on commas and take the first part.
// The backend denormalizes the user list in the room table via csv.
return username.split(',')[0].trim();
}
// Function to sync dropdowns and update the query string
function syncDropdownsAndQueryString() {
const sanitizedUsername = sanitizeUsername(username);
const modelSelectDesktop = document.getElementById("model-select");
const voiceSelectDesktop = document.getElementById("voice-select");
const modelSelectMobile = document.getElementById("model-select-mobile");
const voiceSelectMobile = document.getElementById("voice-select-mobile");
// Determine the current model and voice from any dropdown
const currentModel = VALID_MODELS.includes(modelSelectDesktop.value) ? modelSelectDesktop.value : 'None';
const currentVoice = VALID_VOICES.includes(voiceSelectDesktop.value) ? voiceSelectDesktop.value : 'onyx';
// Sync both desktop and mobile dropdowns
modelSelectDesktop.value = currentModel;
voiceSelectDesktop.value = currentVoice;
modelSelectMobile.value = currentModel;
voiceSelectMobile.value = currentVoice;
const newUrl = new URL(window.location.href);
newUrl.searchParams.set("username", sanitizedUsername);
newUrl.searchParams.set("model", currentModel);
newUrl.searchParams.set("voice", currentVoice);
window.history.replaceState({}, '', newUrl);
}
document.addEventListener('DOMContentLoaded', (event) => {
const chatContainer = document.getElementById("chat");
const modelSelectDesktop = document.getElementById("model-select");
const voiceSelectDesktop = document.getElementById("voice-select");
const modelSelectMobile = document.getElementById("model-select-mobile");
const voiceSelectMobile = document.getElementById("voice-select-mobile");
chatContainer.addEventListener('scroll', () => {
const distanceFromBottom = chatContainer.scrollHeight - chatContainer.scrollTop - chatContainer.clientHeight;
@ -107,57 +147,96 @@ document.addEventListener('DOMContentLoaded', (event) => {
});
// Set initial model and voice from query string
const modelSelect = document.getElementById("model-select");
const voiceSelect = document.getElementById("voice-select");
const initialModel = urlParams.get("model") || "None";
const initialVoice = urlParams.get("voice") || "onyx";
modelSelect.value = initialModel;
voiceSelect.value = initialVoice;
modelSelectDesktop.value = initialModel;
voiceSelectDesktop.value = initialVoice;
modelSelectMobile.value = initialModel;
voiceSelectMobile.value = initialVoice;
// Update query string when model or voice changes
modelSelect.addEventListener("change", updateQueryString);
voiceSelect.addEventListener("change", updateQueryString);
// Add event listeners for desktop dropdowns
modelSelectDesktop.addEventListener("change", () => {
if (isSyncingDropdowns) return;
isSyncingDropdowns = true;
modelSelectMobile.value = modelSelectDesktop.value;
syncDropdownsAndQueryString();
isSyncingDropdowns = false;
});
voiceSelectDesktop.addEventListener("change", () => {
if (isSyncingDropdowns) return;
isSyncingDropdowns = true;
voiceSelectMobile.value = voiceSelectDesktop.value;
syncDropdownsAndQueryString();
isSyncingDropdowns = false;
});
// Add event listeners for mobile dropdowns
modelSelectMobile.addEventListener("change", () => {
if (isSyncingDropdowns) return;
isSyncingDropdowns = true;
modelSelectDesktop.value = modelSelectMobile.value;
syncDropdownsAndQueryString();
isSyncingDropdowns = false;
});
voiceSelectMobile.addEventListener("change", () => {
if (isSyncingDropdowns) return;
isSyncingDropdowns = true;
voiceSelectDesktop.value = voiceSelectMobile.value;
syncDropdownsAndQueryString();
isSyncingDropdowns = false;
});
});
// Function to validate and update the query string
function updateQueryString() {
// Socket event when the user connects
socket.on("connect", () => {
// Sanitize the username before joining
const sanitizedUsername = sanitizeUsername(username);
const modelSelect = document.getElementById("model-select");
const voiceSelect = document.getElementById("voice-select");
// Validate model and voice
const model = VALID_MODELS.includes(modelSelect.value) ? modelSelect.value : 'None';
const voice = VALID_VOICES.includes(voiceSelect.value) ? voiceSelect.value : 'onyx';
const newUrl = new URL(window.location.href);
newUrl.searchParams.set("username", sanitizedUsername);
newUrl.searchParams.set("model", model);
newUrl.searchParams.set("voice", voice);
window.history.replaceState({}, '', newUrl);
}
socket.emit("join", {"username": sanitizedUsername, "room_name": room_name});
// Sync dropdowns and update the query string
syncDropdownsAndQueryString();
});
// Function to update the active and inactive user lists in the DOM
function updateUserLists(activeUsers, inactiveUsers) {
const activeUserListElement = document.getElementById("active-users");
const inactiveUserListElement = document.getElementById("inactive-users");
const activeUserListElementMobile = document.getElementById("active-users-mobile");
const inactiveUserListElementMobile = document.getElementById("inactive-users-mobile");
activeUserListElement.innerHTML = ''; // Clear the current list
inactiveUserListElement.innerHTML = ''; // Clear the current list
activeUserListElementMobile.innerHTML = ''; // Clear the current list (mobile)
inactiveUserListElementMobile.innerHTML = ''; // Clear the current list (mobile)
// Populate the list with active users
// Populate the list with active users (desktop)
activeUsers.forEach(username => {
const userItem = document.createElement("li");
userItem.textContent = username;
activeUserListElement.appendChild(userItem);
});
// Populate the list with inactive users
// Populate the list with inactive users (desktop)
inactiveUsers.forEach(username => {
const userItem = document.createElement("li");
userItem.textContent = username;
inactiveUserListElement.appendChild(userItem);
});
// Populate the list with active users (mobile)
activeUsers.forEach(username => {
const userItemMobile = document.createElement("li");
userItemMobile.textContent = username;
activeUserListElementMobile.appendChild(userItemMobile);
});
// Populate the list with inactive users (mobile)
inactiveUsers.forEach(username => {
const userItemMobile = document.createElement("li");
userItemMobile.textContent = username;
inactiveUserListElementMobile.appendChild(userItemMobile);
});
}
// Update user lists whenever the event is received
@ -171,11 +250,10 @@ function sendMessage() {
const model = document.getElementById("model-select").value;
let messageToSend = message.trim();
if (model !== "None") {
messageToSend = `${model} ${messageToSend}`;
}
if (messageToSend !== "") { // Ensure we're not sending empty messages
if (model !== "None") {
messageToSend = `${model} ${messageToSend}`;
}
socket.emit("chat_message", {"username": username, "message": messageToSend, "room_name": room_name});
document.getElementById("message").value = "";
}
@ -216,22 +294,6 @@ socket.on('update_room_list', function(updatedRoom) {
}
});
// Function to sanitize the username
function sanitizeUsername(username) {
// Split the username on commas and take the first part.
// The backend denormalizes the user list in the room table via csv.
return username.split(',')[0].trim();
}
// Socket event when the user connects
socket.on("connect", () => {
// Sanitize the username before joining
const sanitizedUsername = sanitizeUsername(username);
socket.emit("join", {"username": sanitizedUsername, "room_name": room_name});
// Update the query string with the sanitized username
updateQueryString();
});
// Function to read text using TTS
async function speakText(text, playButton, messageId) {
const voice = document.getElementById("voice-select").value;