// Global state let conversationHistory = JSON.parse(localStorage.getItem('claudeChat_history')) || []; let selectedModel = localStorage.getItem('claudeChat_selectedModel') || 'claude-sonnet-4-5'; let isProcessing = false; let isStreaming = false; // DOM Elements const modelSelector = document.getElementById('model-selector'); const messageInput = document.getElementById('message-input'); const sendBtn = document.getElementById('send-btn'); const streamBtn = document.getElementById('stream-btn'); const chatContainer = document.getElementById('chat-container'); const messagesContainer = document.getElementById('messages'); const examplePrompts = document.getElementById('example-prompts'); const exampleButtons = document.querySelectorAll('.example-btn'); const statusBar = document.getElementById('status-bar'); const statusMessage = document.getElementById('status-message'); const retryBtn = document.getElementById('retry-btn'); const typingIndicator = document.getElementById('typing-indicator'); const charCount = document.getElementById('char-count'); const newChatBtn = document.getElementById('new-chat-btn'); const appTitle = document.getElementById('app-title'); // Initialize document.addEventListener('DOMContentLoaded', () => { // Set initial model modelSelector.value = selectedModel; // Load conversation history renderConversationHistory(); // Set up event listeners setupEventListeners(); // Update character count updateCharCount(); }); // Set up event listeners function setupEventListeners() { // Model selection modelSelector.addEventListener('change', handleModelChange); // Message input messageInput.addEventListener('input', updateCharCount); messageInput.addEventListener('keydown', handleKeyDown); // Send buttons sendBtn.addEventListener('click', () => sendMessage(false)); streamBtn.addEventListener('click', () => sendMessage(true)); // Example prompts exampleButtons.forEach(button => { button.addEventListener('click', () => { messageInput.value = button.textContent; messageInput.focus(); updateCharCount(); }); }); // Status bar retryBtn.addEventListener('click', retryLastMessage); // New chat newChatBtn.addEventListener('click', resetConversation); appTitle.addEventListener('click', resetConversation); // Auto-scroll chatContainer.addEventListener('scroll', handleScroll); } // Handle model change function handleModelChange() { const newModel = modelSelector.value; const previousModel = selectedModel; selectedModel = newModel; localStorage.setItem('claudeChat_selectedModel', selectedModel); // If conversation exists, add system message if (conversationHistory.length > 0) { const modelName = modelSelector.options[modelSelector.selectedIndex].text; addSystemMessage(`Model changed to ${modelName} — previous context retained`); } } // Handle keyboard shortcuts function handleKeyDown(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if (!isProcessing && messageInput.value.trim()) { sendMessage(false); } } // Clear input shortcut if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); messageInput.value = ''; updateCharCount(); } // New chat shortcut if ((e.ctrlKey || e.metaKey) && e.key === 'n') { e.preventDefault(); resetConversation(); } } // Update character count function updateCharCount() { const count = messageInput.value.length; charCount.textContent = `${count}/10000`; if (count > 8000) { charCount.classList.add('text-red-500'); } else { charCount.classList.remove('text-red-500'); } // Enable/disable buttons const hasText = count > 0; sendBtn.disabled = !hasText || isProcessing; streamBtn.disabled = !hasText || isProcessing; } // Send message async function sendMessage(stream = false) { const message = messageInput.value.trim(); if (!message || isProcessing) return; isProcessing = true; isStreaming = stream; updateUIState(); // Add user message to UI addUserMessage(message); // Clear input messageInput.value = ''; updateCharCount(); // Show typing indicator for non-streaming if (!stream) { typingIndicator.classList.remove('hidden'); chatContainer.scrollTop = chatContainer.scrollHeight; } try { // Add to history conversationHistory.push({ role: 'user', content: message }); saveConversation(); // Call API if (stream) { await streamResponse(message); } else { await getResponse(message); } } catch (error) { handleError(error); } finally { isProcessing = false; isStreaming = false; updateUIState(); typingIndicator.classList.add('hidden'); } } // Get response from API (non-streaming) async function getResponse(userMessage) { try { const response = await puter.ai.chat(userMessage, { model: selectedModel, conversationHistory: conversationHistory }); const assistantMessage = response.message?.content?.[0]?.text || "Sorry, I couldn't process that."; // Add to history conversationHistory.push({ role: 'assistant', content: assistantMessage }); saveConversation(); // Add to UI addAssistantMessage(assistantMessage); } catch (error) { throw error; } } // Stream response from API async function streamResponse(userMessage) { try { const response = await puter.ai.chat(userMessage, { model: selectedModel, conversationHistory: conversationHistory, stream: true }); // Create assistant message element const messageElement = document.createElement('div'); messageElement.className = 'assistant-message bg-white p-4 rounded-lg shadow-sm'; messageElement.innerHTML = `
A
Assistant
`; const contentElement = messageElement.querySelector('.text-gray-700'); messagesContainer.appendChild(messageElement); chatContainer.scrollTop = chatContainer.scrollHeight; let fullResponse = ''; // Process stream for await (const chunk of response) { const text = chunk?.message?.content?.[0]?.text || ''; fullResponse += text; contentElement.textContent = fullResponse; // Scroll to bottom chatContainer.scrollTop = chatContainer.scrollHeight; } // Add to history conversationHistory.push({ role: 'assistant', content: fullResponse }); saveConversation(); } catch (error) { throw error; } } // Add user message to UI function addUserMessage(content) { const messageElement = document.createElement('div'); messageElement.className = 'user-message bg-blue-500 text-white p-4 rounded-lg shadow-sm ml-auto'; messageElement.innerHTML = `
Y
You
${content}
`; messagesContainer.appendChild(messageElement); chatContainer.scrollTop = chatContainer.scrollHeight; // Hide example prompts after first message if (conversationHistory.length === 0) { examplePrompts.classList.add('hidden'); } } // Add assistant message to UI function addAssistantMessage(content) { const messageElement = document.createElement('div'); messageElement.className = 'assistant-message bg-white p-4 rounded-lg shadow-sm'; messageElement.innerHTML = `
A
Assistant
${content}
`; messagesContainer.appendChild(messageElement); chatContainer.scrollTop = chatContainer.scrollHeight; } // Add system message to UI function addSystemMessage(content) { const messageElement = document.createElement('div'); messageElement.className = 'system-message bg-gray-100 text-gray-600 p-3 rounded-lg text-center text-sm my-2'; messageElement.textContent = content; messagesContainer.appendChild(messageElement); chatContainer.scrollTop = chatContainer.scrollHeight; } // Handle errors function handleError(error) { console.error('Error:', error); showStatus(`Request failed: ${error.message}`, 'error'); retryBtn.classList.remove('hidden'); } // Retry last message function retryLastMessage() { if (conversationHistory.length >= 2) { const lastUserMessage = conversationHistory[conversationHistory.length - 2].content; messageInput.value = lastUserMessage; updateCharCount(); retryBtn.classList.add('hidden'); sendMessage(isStreaming); } } // Reset conversation function resetConversation() { conversationHistory = []; localStorage.removeItem('claudeChat_history'); messagesContainer.innerHTML = `
A
Assistant

Hello! I'm Claude, an AI assistant. How can I help you today?

`; examplePrompts.classList.remove('hidden'); hideStatus(); } // Save conversation to localStorage function saveConversation() { localStorage.setItem('claudeChat_history', JSON.stringify(conversationHistory)); localStorage.setItem('claudeChat_timestamp', Date.now()); } // Render conversation history function renderConversationHistory() { if (conversationHistory.length === 0) return; messagesContainer.innerHTML = ''; examplePrompts.classList.add('hidden'); conversationHistory.forEach(msg => { if (msg.role === 'user') { addUserMessage(msg.content); } else if (msg.role === 'assistant') { addAssistantMessage(msg.content); } }); } // Update UI state based on processing status function updateUIState() { if (isProcessing) { messageInput.disabled = true; sendBtn.disabled = true; streamBtn.disabled = true; modelSelector.disabled = true; messageInput.placeholder = "Waiting for response..."; } else { messageInput.disabled = false; updateCharCount(); modelSelector.disabled = false; messageInput.placeholder = "Type your message here... (Shift+Enter for new line)"; } } // Show status message function showStatus(message, type) { statusMessage.textContent = message; statusBar.className = `px-6 py-3 text-sm text-white ${type}`; statusBar.classList.remove('hidden'); // Auto-hide success messages if (type === 'success') { setTimeout(hideStatus, 3000); } // Auto-hide error messages (but keep retry button) if (type === 'error') { setTimeout(() => { if (!retryBtn.classList.contains('hidden')) return; hideStatus(); }, 5000); } } // Hide status message function hideStatus() { statusBar.classList.add('hidden'); retryBtn.classList.add('hidden'); } // Handle scroll for auto-scroll detection function handleScroll() { const threshold = 50; const atBottom = chatContainer.scrollTop + chatContainer.clientHeight >= chatContainer.scrollHeight - threshold; // In a real implementation, you might add a "scroll to bottom" button here // when the user scrolls up and new messages arrive }