|
|
|
|
|
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'); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const appState = new ClaudeAppState(); |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
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) { |
|
|
|
|
|
const chatContainer = document.querySelector('claude-chat-container'); |
|
|
if (chatContainer && chatContainer.updateStreamingMessage) { |
|
|
chatContainer.updateStreamingMessage(content); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
const api = new ClaudeAPI(); |
|
|
|
|
|
|
|
|
document.addEventListener('claude-send-message', async (event) => { |
|
|
const { message, stream } = event.detail; |
|
|
|
|
|
try { |
|
|
|
|
|
appState.addMessage('user', message); |
|
|
|
|
|
|
|
|
document.dispatchEvent(new CustomEvent('claude-show-typing')); |
|
|
|
|
|
const response = await api.sendMessage(message, stream); |
|
|
|
|
|
|
|
|
document.dispatchEvent(new CustomEvent('claude-hide-typing')); |
|
|
appState.addMessage('assistant', response); |
|
|
|
|
|
|
|
|
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')); |
|
|
}); |
|
|
|
|
|
|
|
|
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')); |
|
|
} |
|
|
}); |
|
|
}); |