Spaces:
Running
Running
document.addEventListener('DOMContentLoaded', () => { | |
// DOM Elements | |
const chatForm = document.getElementById('chat-form'); | |
const userInput = document.getElementById('user-input'); | |
const chatMessages = document.getElementById('chat-messages'); | |
const sendButton = document.getElementById('send-button'); | |
const typingIndicator = document.getElementById('typing-indicator'); | |
const clearChatButton = document.getElementById('clear-chat'); | |
const themeToggle = document.getElementById('theme-toggle'); | |
const charCounter = document.getElementById('char-counter'); | |
const toastContainer = document.getElementById('toast-container'); | |
// State | |
let darkMode = document.documentElement.classList.contains('dark'); | |
let retryCount = 0; | |
// Event Listeners | |
chatForm.addEventListener('submit', handleSubmit); | |
clearChatButton.addEventListener('click', clearChat); | |
themeToggle.addEventListener('click', toggleTheme); | |
userInput.addEventListener('input', handleInput); | |
userInput.addEventListener('keydown', handleKeydown); | |
document.addEventListener('keydown', handleHotkeys); | |
// Initialize | |
loadChat(); | |
updateThemeButton(); | |
userInput.focus(); | |
function handleInput(e) { | |
const length = e.target.value.length; | |
charCounter.textContent = `${length}/${config.MAX_CHARS}`; | |
autoResize(); | |
} | |
function autoResize() { | |
userInput.style.height = 'auto'; | |
userInput.style.height = userInput.scrollHeight + 'px'; | |
} | |
function handleKeydown(e) { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault(); | |
chatForm.requestSubmit(); | |
} | |
} | |
function handleHotkeys(e) { | |
if (e.ctrlKey || e.metaKey) { | |
switch(e.key) { | |
case '/': | |
e.preventDefault(); | |
userInput.focus(); | |
break; | |
case 'l': | |
e.preventDefault(); | |
clearChat(); | |
break; | |
} | |
} | |
} | |
async function handleSubmit(e) { | |
e.preventDefault(); | |
const message = userInput.value.trim(); | |
if (message) { | |
addMessage('user', message); | |
userInput.value = ''; | |
userInput.style.height = 'auto'; | |
charCounter.textContent = `0/${config.MAX_CHARS}`; | |
disableInput(true); | |
showTypingIndicator(); | |
try { | |
const response = await sendMessageWithRetry(message); | |
addMessage('bot', response); | |
showToast('Message sent successfully!', 'success'); | |
} catch (error) { | |
console.error('Error:', error); | |
showToast('Failed to send message. Please try again.', 'error'); | |
} | |
disableInput(false); | |
hideTypingIndicator(); | |
userInput.focus(); | |
} | |
} | |
async function sendMessageWithRetry(message, attempt = 1) { | |
try { | |
return await sendMessageToBot(message); | |
} catch (error) { | |
if (attempt < config.MAX_RETRIES) { | |
await new Promise(resolve => setTimeout(resolve, config.RETRY_DELAY * attempt)); | |
return sendMessageWithRetry(message, attempt + 1); | |
} | |
throw error; | |
} | |
} | |
async function sendMessageToBot(message) { | |
const response = await fetch(config.COHERE_API_URL, { | |
method: 'POST', | |
headers: { | |
'Authorization': `BEARER ${config.COHERE_API_KEY}`, | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ | |
model: 'command-nightly', | |
prompt: `Human: ${message}\n${config.BOT_NAME}:`, | |
max_tokens: 150, | |
temperature: 0.7, | |
k: 20, | |
stop_sequences: ["Human:", "\n"], | |
return_likelihoods: 'NONE', | |
}), | |
}); | |
if (!response.ok) { | |
throw new Error('Failed to fetch from Cohere API'); | |
} | |
const data = await response.json(); | |
return data.generations[0].text.trim(); | |
} | |
function addMessage(sender, content) { | |
const messageElement = document.createElement('div'); | |
messageElement.classList.add('message', `${sender}-message`); | |
messageElement.textContent = content; | |
// Add copy button | |
const copyButton = document.createElement('button'); | |
copyButton.classList.add('copy-button'); | |
copyButton.innerHTML = '<i class="fas fa-copy"></i>'; | |
copyButton.addEventListener('click', () => copyToClipboard(content)); | |
messageElement.appendChild(copyButton); | |
// Add for screen readers | |
messageElement.setAttribute('role', 'log'); | |
messageElement.setAttribute('aria-label', `${sender} message: ${content}`); | |
chatMessages.appendChild(messageElement); | |
messageElement.scrollIntoView({ behavior: 'smooth' }); | |
saveChat(); | |
} | |
function copyToClipboard(text) { | |
navigator.clipboard.writeText(text).then(() => { | |
showToast('Copied to clipboard!', 'success'); | |
}).catch(err => { | |
console.error('Failed to copy: ', err); | |
showToast('Failed to copy. Please try again.', 'error'); | |
}); | |
} | |
function showTypingIndicator() { | |
typingIndicator.classList.remove('hidden'); | |
typingIndicator.scrollIntoView({ behavior: 'smooth' }); | |
} | |
function hideTypingIndicator() { | |
typingIndicator.classList.add('hidden'); | |
} | |
function disableInput(disabled) { | |
userInput.disabled = disabled; | |
sendButton.disabled = disabled; | |
} | |
function showToast(message, type = 'info') { | |
const toast = document.createElement('div'); | |
toast.classList.add('toast', type); | |
toast.textContent = message; | |
toastContainer.appendChild(toast); | |
setTimeout(() => { | |
toast.style.opacity = '0'; | |
setTimeout(() => toast.remove(), 300); | |
}, 3000); | |
} | |
function saveChat() { | |
const messages = Array.from(chatMessages.children) | |
.filter(el => el.classList.contains('message')) | |
.map(msg => ({ | |
sender: msg.classList.contains('user-message') ? 'user' : 'bot', | |
content: msg.textContent | |
})); | |
localStorage.setItem('chatHistory', JSON.stringify(messages)); | |
} | |
function loadChat() { | |
const savedChat = localStorage.getItem('chatHistory'); | |
if (savedChat) { | |
const messages = JSON.parse(savedChat); | |
chatMessages.innerHTML = ''; | |
messages.forEach(msg => addMessage(msg.sender, msg.content)); | |
} | |
} | |
function clearChat() { | |
chatMessages.innerHTML = ''; | |
localStorage.removeItem('chatHistory'); | |
addMessage('bot', config.WELCOME_MESSAGE); | |
showToast('Chat cleared', 'success'); | |
} | |
function toggleTheme() { | |
darkMode = !darkMode; | |
document.documentElement.classList.toggle('dark', darkMode); | |
localStorage.setItem('theme', darkMode ? 'dark' : 'light'); | |
updateThemeButton(); | |
showToast(`${darkMode ? 'Dark' : 'Light'} mode activated`, 'success'); | |
} | |
function updateThemeButton() { | |
themeToggle.innerHTML = `<i class="fas fa-${darkMode ? 'sun' : 'moon'}"></i>`; | |
} | |
}); | |