quantumkv's picture
Hi Design Team,
20fda5d verified
// 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'));
}
});
});