Enhance user experience with multiple improvements

- Add username field to right sidebar and mobile modal with 'guest' default
- Implement real-time username sync with URL query string updates
- Add opencompletion.com button and new room creation in left sidebar
- Implement room name slugification (e.g. "a whole new world" → "a-whole-new-world")
- Create shared utils.js for common functions like slugify
- Add single search result auto-redirect functionality
- Remove redundant UI elements ("Create New Room" header, docs link)
- Preserve user settings (username, model, voice) across redirects and room creation

Technical improvements:
- Consolidated duplicate code into shared utility functions
- Enhanced search logic with parameter preservation
- Improved mobile/desktop sync for all input fields
- Better URL handling and query string management
This commit is contained in:
Russell Ballestrini 2025-08-11 16:01:51 -04:00
parent 88f68e4adc
commit acdf653eaa
5 changed files with 267 additions and 17 deletions

View file

@ -20,6 +20,10 @@
<a href="/download_chat_history_md?room_name={{ room_name }}" download="{{ room_name }}.md">Markdown</a>
</div>
<br>
<div>
<label for="username-input">Username</label>
<input type="text" id="username-input" placeholder="guest" maxlength="50" style="width: 100%; padding: 4px; margin-top: 2px; border: 1px solid #ccc; border-radius: 3px;">
</div>
<div>
<label for="model-select">Model</label>
<select id="model-select">
@ -82,7 +86,14 @@
const API_KEY = "dummy-api-key";
const TTS_API_URL = "https://speech.ai.unturf.com/v1/audio/speech";
const urlParams = new URLSearchParams(window.location.search);
const username = urlParams.get("username");
let username = urlParams.get("username") || "guest"; // Default to "guest" if no username in URL
// If no username was in the URL, add it now
if (!urlParams.get("username")) {
const newUrl = new URL(window.location.href);
newUrl.searchParams.set("username", username);
window.history.replaceState({}, '', newUrl);
}
const room_name = "{{ room_name }}";
// Global constants for valid voices
@ -119,24 +130,35 @@ function sanitizeUsername(username) {
return username.split(',')[0].trim();
}
// Function to sync dropdowns and update the query string
function syncDropdownsAndQueryString() {
const sanitizedUsername = sanitizeUsername(username);
// Function to sync all inputs and update the query string
function syncInputsAndQueryString() {
const usernameInputDesktop = document.getElementById("username-input");
const usernameInputMobile = document.getElementById("username-input-mobile");
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
// Get current values
const currentUsername = usernameInputDesktop?.value || username || "guest";
const currentModel = modelSelectDesktop.value;
const currentVoice = VALID_VOICES.includes(voiceSelectDesktop.value) ? voiceSelectDesktop.value : 'onyx';
// Sync both desktop and mobile dropdowns
// Update global username variable
username = currentUsername;
const sanitizedUsername = sanitizeUsername(username);
// Sync username inputs
if (usernameInputDesktop) usernameInputDesktop.value = sanitizedUsername;
if (usernameInputMobile) usernameInputMobile.value = sanitizedUsername;
// Sync dropdowns
modelSelectDesktop.value = currentModel;
voiceSelectDesktop.value = currentVoice;
modelSelectMobile.value = currentModel;
voiceSelectMobile.value = currentVoice;
if (modelSelectMobile) modelSelectMobile.value = currentModel;
if (voiceSelectMobile) voiceSelectMobile.value = currentVoice;
// Update URL
const newUrl = new URL(window.location.href);
newUrl.searchParams.set("username", sanitizedUsername);
newUrl.searchParams.set("model", currentModel);
@ -144,6 +166,11 @@ function syncDropdownsAndQueryString() {
window.history.replaceState({}, '', newUrl);
}
// Backward compatibility
function syncDropdownsAndQueryString() {
syncInputsAndQueryString();
}
document.addEventListener('DOMContentLoaded', (event) => {
const chatContainer = document.getElementById("chat");
const modelSelectDesktop = document.getElementById("model-select");
@ -204,13 +231,21 @@ document.addEventListener('DOMContentLoaded', (event) => {
userHasScrolledUp = distanceFromBottom > 5;
});
// Set initial model and voice from query string
// Set initial model, voice, and username from query string
const initialModel = urlParams.get("model") || "None";
const initialVoice = urlParams.get("voice") || "onyx";
const initialUsername = username; // Already set to URL param or "guest"
modelSelectDesktop.value = initialModel;
voiceSelectDesktop.value = initialVoice;
modelSelectMobile.value = initialModel;
voiceSelectMobile.value = initialVoice;
// Set initial username values
const usernameInputDesktop = document.getElementById("username-input");
const usernameInputMobile = document.getElementById("username-input-mobile");
if (usernameInputDesktop) usernameInputDesktop.value = initialUsername;
if (usernameInputMobile) usernameInputMobile.value = initialUsername;
// Add event listeners for desktop dropdowns
modelSelectDesktop.addEventListener("change", () => {
@ -245,6 +280,39 @@ document.addEventListener('DOMContentLoaded', (event) => {
syncDropdownsAndQueryString();
isSyncingDropdowns = false;
});
// Add event listeners for username inputs
if (usernameInputDesktop) {
usernameInputDesktop.addEventListener("input", () => {
if (isSyncingDropdowns) return;
isSyncingDropdowns = true;
if (usernameInputMobile) {
usernameInputMobile.value = usernameInputDesktop.value;
}
syncInputsAndQueryString();
isSyncingDropdowns = false;
});
usernameInputDesktop.addEventListener("blur", () => {
syncInputsAndQueryString();
});
}
if (usernameInputMobile) {
usernameInputMobile.addEventListener("input", () => {
if (isSyncingDropdowns) return;
isSyncingDropdowns = true;
if (usernameInputDesktop) {
usernameInputDesktop.value = usernameInputMobile.value;
}
syncInputsAndQueryString();
isSyncingDropdowns = false;
});
usernameInputMobile.addEventListener("blur", () => {
syncInputsAndQueryString();
});
}
});
// Socket event when the user connects
@ -252,8 +320,8 @@ socket.on("connect", () => {
// Sanitize the username before joining
const sanitizedUsername = sanitizeUsername(username);
socket.emit("join", {"username": sanitizedUsername, "room_name": room_name});
// Sync dropdowns and update the query string
syncDropdownsAndQueryString();
// Sync inputs and update the query string
syncInputsAndQueryString();
});
// Function to update the active and inactive user lists in the DOM