claude-ai-chat / script.js
quantumkv's picture
Hi Design Team,
e3a84af verified
// 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 = `
<div class="flex items-center mb-2">
<div class="w-6 h-6 rounded-full bg-purple-500 flex items-center justify-center mr-2">
<span class="text-white text-xs">A</span>
</div>
<span class="font-semibold text-gray-700">Assistant</span>
</div>
<div class="text-gray-700"></div>
`;
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 = `
<div class="flex items-center mb-2">
<div class="w-6 h-6 rounded-full bg-blue-300 flex items-center justify-center mr-2">
<span class="text-blue-800 text-xs">Y</span>
</div>
<span class="font-semibold">You</span>
</div>
<div class="text-white">${content}</div>
`;
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 = `
<div class="flex items-center mb-2">
<div class="w-6 h-6 rounded-full bg-purple-500 flex items-center justify-center mr-2">
<span class="text-white text-xs">A</span>
</div>
<span class="font-semibold text-gray-700">Assistant</span>
</div>
<div class="text-gray-700">${content}</div>
`;
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 = `
<div class="assistant-message bg-white p-4 rounded-lg shadow-sm">
<div class="flex items-center mb-2">
<div class="w-6 h-6 rounded-full bg-purple-500 flex items-center justify-center mr-2">
<span class="text-white text-xs">A</span>
</div>
<span class="font-semibold text-gray-700">Assistant</span>
</div>
<div class="text-gray-700">
<p>Hello! I'm Claude, an AI assistant. How can I help you today?</p>
</div>
</div>
`;
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
}