File size: 7,886 Bytes
20fda5d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
// 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'));
}
});
}); |