// Global state management and utility functions class ClaudeAppState { constructor() { this.loadFromStorage(); } loadFromStorage() { try { const history = localStorage.getItem('claudeChat_history'); const model = localStorage.getItem('claudeChat_selectedModel'); const timestamp = localStorage.getItem('claudeChat_timestamp'); // Validate timestamp (clear if older than 24 hours) if (timestamp && Date.now() - parseInt(timestamp) > 24 * 60 * 60 * 1000) { this.clearStorage(); return; } if (history) { window.claudeApp.conversationHistory = JSON.parse(history); } if (model) { window.claudeApp.selectedModel = model; } } catch (error) { console.error('Error loading from storage:', error); this.clearStorage(); } } saveToStorage() { try { localStorage.setItem('claudeChat_history', JSON.stringify(window.claudeApp.conversationHistory)); localStorage.setItem('claudeChat_selectedModel', window.claudeApp.selectedModel); localStorage.setItem('claudeChat_timestamp', Date.now().toString()); } catch (error) { console.error('Error saving to storage:', error); } } clearStorage() { localStorage.removeItem('claudeChat_history'); localStorage.removeItem('claudeChat_selectedModel'); localStorage.removeItem('claudeChat_timestamp'); window.claudeApp.conversationHistory = []; window.claudeApp.selectedModel = 'claude-sonnet-4-5'; } addMessage(role, content, timestamp = Date.now()) { const message = { role, content, timestamp, id: this.generateId() }; window.claudeApp.conversationHistory.push(message); // Maintain 50 message limit if (window.claudeApp.conversationHistory.length > 50) { window.claudeApp.conversationHistory.shift(); } this.saveToStorage(); return message; } generateId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } setModel(model) { const oldModel = window.claudeApp.selectedModel; window.claudeApp.selectedModel = model; // Add system message if switching models mid-conversation if (window.claudeApp.conversationHistory.length > 0 && oldModel !== model) { this.addMessage('system', `Model changed to ${this.getModelLabel(model)} — previous context retained`); } this.saveToStorage(); } getModelLabel(model) { const modelLabels = { 'claude-sonnet-4-5': 'Claude Sonnet 4.5', 'claude-sonnet-4': 'Claude Sonnet 4', 'claude-opus-4-1': 'Claude Opus 4.1', 'claude-opus-4': 'Claude Opus 4', 'claude-haiku-4-5': 'Claude Haiku 4.5' }; return modelLabels[model] || model; } } // Initialize app state const appState = new ClaudeAppState(); // API Communication class ClaudeAPI { async sendMessage(message, stream = false) { if (window.claudeApp.isProcessing) { throw new Error('Another request is already in progress'); } window.claudeApp.isProcessing = true; window.claudeApp.isStreaming = stream; try { const options = { model: window.claudeApp.selectedModel, stream: stream }; if (stream) { return this.sendStreamingMessage(message, options); } else { return this.sendStandardMessage(message, options); } } catch (error) { window.claudeApp.isProcessing = false; window.claudeApp.isStreaming = false; throw error; } } async sendStandardMessage(message, options) { try { const response = await puter.ai.chat(message, options); window.claudeApp.isProcessing = false; return response.message.content[0].text; } catch (error) { window.claudeApp.isProcessing = false; throw new Error(`API Error: ${error.message}`); } } async sendStreamingMessage(message, options) { try { const response = await puter.ai.chat(message, options); let fullResponse = ''; for await (const part of response) { if (part.type === 'content_block_delta' && part.delta?.text) { fullResponse += part.delta.text; // Update the UI with the current response this.updateStreamingResponse(fullResponse); } } window.claudeApp.isProcessing = false; window.claudeApp.isStreaming = false; return fullResponse; } catch (error) { window.claudeApp.isProcessing = false; window.claudeApp.isStreaming = false; throw new Error(`Streaming Error: ${error.message}`); } } updateStreamingResponse(content) { // This will be implemented in the chat-container component const chatContainer = document.querySelector('claude-chat-container'); if (chatContainer && chatContainer.updateStreamingMessage) { chatContainer.updateStreamingMessage(content); } } } // Event handling and coordination document.addEventListener('DOMContentLoaded', function() { const api = new ClaudeAPI(); // Global event listeners for cross-component communication document.addEventListener('claude-send-message', async (event) => { const { message, stream } = event.detail; try { // Add user message to history appState.addMessage('user', message); // Show typing indicator document.dispatchEvent(new CustomEvent('claude-show-typing')); const response = await api.sendMessage(message, stream); // Hide typing indicator and add assistant response document.dispatchEvent(new CustomEvent('claude-hide-typing')); appState.addMessage('assistant', response); // Show success status document.dispatchEvent(new CustomEvent('claude-show-status', { detail: { type: 'success', message: 'Response received' } })); } catch (error) { document.dispatchEvent(new CustomEvent('claude-hide-typing')); document.dispatchEvent(new CustomEvent('claude-show-status', { detail: { type: 'error', message: error.message } })); } }); document.addEventListener('claude-change-model', (event) => { const { model } = event.detail; appState.setModel(model); }); document.addEventListener('claude-new-chat', () => { appState.clearStorage(); document.dispatchEvent(new CustomEvent('claude-clear-chat')); }); // Keyboard shortcuts document.addEventListener('keydown', (event) => { const isCmdK = (event.metaKey || event.ctrlKey) && event.key === 'k'; const isCmdN = (event.metaKey || event.ctrlKey) && event.key === 'n'; if (isCmdK) { event.preventDefault(); document.dispatchEvent(new CustomEvent('claude-clear-input')); } if (isCmdN) { event.preventDefault(); document.dispatchEvent(new CustomEvent('claude-new-chat')); } }); });