CareerPal / static /script.js
SpanDone's picture
Fixed Recurring Problem of Mobile UI
fdf1b24
document.addEventListener("DOMContentLoaded", () => {
// --- Get all necessary elements ---
const chatForm = document.getElementById("chat-form");
const messageInput = document.getElementById("message-input");
const chatMessages = document.getElementById("chat-messages");
const themeToggle = document.getElementById("theme-toggle");
const sendButton = chatForm.querySelector("button[type='submit']");
const leftColumn = document.getElementById("left-column");
const uploadResumeButton = document.getElementById("upload-resume-button");
const resumeFileInput = document.getElementById("resume-file-input");
const refreshChatButton = document.getElementById("refresh-chat-button");
const menuToggle = document.getElementById("menu-toggle");
// --- Elements for mobile menu ---
const closeMenuButton = document.getElementById("close-menu-button");
const pageOverlay = document.getElementById("page-overlay");
let conversationState = {};
// --- Functions to open and close the mobile menu ---
const openMenu = () => {
leftColumn.classList.add('active');
pageOverlay.classList.add('active');
document.body.classList.add('menu-open'); // Prevents background scroll
};
const closeMenu = () => {
leftColumn.classList.remove('active');
pageOverlay.classList.remove('active');
document.body.classList.remove('menu-open');
};
// --- Event Listeners for menu control ---
menuToggle.addEventListener('click', openMenu);
closeMenuButton.addEventListener('click', closeMenu);
pageOverlay.addEventListener('click', closeMenu);
const sendMessage = (messageText) => {
messageInput.value = messageText;
const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
chatForm.dispatchEvent(submitEvent);
};
const uploadResumeFile = async (file) => {
if (!file) return;
if (file.size > 5 * 1024 * 1024) { // 5MB limit
addMessage("bot", "The selected file is too large. Please upload a file smaller than 5MB.");
return;
}
addMessage("user", `Uploading Resume: <i>${file.name}</i>`);
showTypingIndicator();
const formData = new FormData();
formData.append("resume_file", file);
try {
const response = await fetch("/upload_resume", {
method: "POST",
body: formData,
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
removeTypingIndicator();
if (data.response) {
let finalResponse = data.response;
finalResponse += "<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='Resume Analyser'>📄 Analyze Another Resume</div><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='End Chat'>🚪 End Chat</div></div>";
addMessage("bot", finalResponse);
conversationState.state = 'awaiting_initial_action';
} else if (data.error) {
addMessage("bot", `Error: ${data.error}`);
conversationState.state = 'awaiting_initial_action';
}
updatePlaceholder();
} catch (error) {
removeTypingIndicator();
console.error("Upload error:", error);
addMessage("bot", "Sorry, I encountered an error while uploading your resume.");
conversationState.state = 'awaiting_initial_action';
updatePlaceholder();
}
resumeFileInput.value = '';
};
chatMessages.addEventListener('click', (event) => {
const card = event.target.closest('.clickable-card');
if (!card) return;
const action = card.dataset.action;
const value = card.dataset.value;
if (action === 'details' || action === 'compare' || action === 'quick_reply') {
sendMessage(value);
const buttonContainer = card.closest('.quick-reply-container');
if (buttonContainer) buttonContainer.remove();
} else if (action === 'restart') {
location.reload();
}
});
leftColumn.addEventListener('click', (event) => {
const featureCard = event.target.closest('[data-action="feature_select"]');
if (!featureCard || conversationState.state === 'asking_questions') {
if (conversationState.state === 'asking_questions') {
const card = document.querySelector('.card');
card.style.transition = 'outline 0.1s ease-out';
card.style.outline = '2px solid #DB2777';
setTimeout(() => { card.style.outline = 'none'; }, 500);
}
return;
}
const featureName = featureCard.dataset.value;
sendMessage(featureName);
closeMenu(); // Close menu after selection
});
const updatePlaceholder = () => {
const currentState = conversationState.state;
let shouldBeEnabled = true;
let placeholderText = "Send a message...";
if (currentState === 'session_ended') {
placeholderText = "Click 'Start Over' to begin a new session.";
shouldBeEnabled = false;
} else if (currentState === 'awaiting_initial_action') {
placeholderText = "Type 'start' or select a feature...";
} else if (currentState === 'awaiting_resume_upload') {
placeholderText = "Upload your resume using the upload button...";
shouldBeEnabled = false;
} else {
placeholderText = "Send a message...";
}
messageInput.placeholder = placeholderText;
messageInput.disabled = !shouldBeEnabled;
sendButton.disabled = !shouldBeEnabled;
uploadResumeButton.style.display = (currentState === 'awaiting_resume_upload') ? 'block' : 'none';
if (shouldBeEnabled) {
messageInput.focus();
}
};
const addMessage = (sender, message) => {
const messageElement = document.createElement("div");
messageElement.classList.add("message", `${sender}-message`);
if (sender === "bot") {
const avatarImg = document.createElement("img");
avatarImg.src = "/static/bot_avatar.png";
avatarImg.alt = "Bot Avatar";
avatarImg.className = "bot-avatar";
messageElement.appendChild(avatarImg);
}
const paragraph = document.createElement("p");
paragraph.innerHTML = message;
messageElement.appendChild(paragraph);
chatMessages.appendChild(messageElement);
// Removed automatic scrolling for bot messages
};
const showTypingIndicator = () => {
messageInput.disabled = true;
sendButton.disabled = true;
if (document.getElementById("typing-indicator")) return;
const indicatorElement = document.createElement("div");
indicatorElement.id = "typing-indicator";
indicatorElement.classList.add("message", "bot-message");
indicatorElement.innerHTML = `<img src="/static/bot_avatar.png" alt="Bot Avatar" class="bot-avatar"><div class="typing-indicator"><span></span><span></span><span></span></div>`;
chatMessages.appendChild(indicatorElement);
chatMessages.scrollTop = chatMessages.scrollHeight; // Scroll only for typing indicator
};
const removeTypingIndicator = () => {
const indicator = document.getElementById("typing-indicator");
if (indicator) indicator.remove();
};
const handleFormSubmit = async (event) => {
event.preventDefault();
const message = messageInput.value.trim();
if (!message) return;
addMessage("user", message);
chatMessages.scrollTop = chatMessages.scrollHeight;
messageInput.value = "";
showTypingIndicator();
try {
const response = await fetch("/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: message, conversation: conversationState }),
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
setTimeout(() => {
removeTypingIndicator();
conversationState = data.conversation;
if (data.response) {
addMessage("bot", data.response);
}
updatePlaceholder();
}, 500);
} catch (error) {
removeTypingIndicator();
console.error("Fetch error:", error);
addMessage("bot", "Sorry, I'm having trouble connecting.");
updatePlaceholder();
}
};
const initializeChat = async () => {
try {
const response = await fetch("/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "", conversation: {} }),
});
const data = await response.json();
conversationState = data.conversation;
addMessage("bot", data.response);
updatePlaceholder();
} catch (error) {
console.error("Initialization error:", error);
addMessage("bot", "Sorry, I'm having trouble connecting.");
}
};
const handleThemeChange = () => {
document.body.className = themeToggle.checked ? 'dark' : 'light';
};
refreshChatButton.addEventListener('click', (event) => {
event.preventDefault();
location.reload();
});
chatForm.addEventListener("submit", handleFormSubmit);
themeToggle.addEventListener("change", handleThemeChange);
uploadResumeButton.addEventListener('click', () => resumeFileInput.click());
resumeFileInput.addEventListener('change', (event) => uploadResumeFile(event.target.files[0]));
document.body.className = 'light';
initializeChat();
});