|
|
|
|
|
class ChatApp { |
|
|
constructor() { |
|
|
this.apiKey = localStorage.getItem('openai-api-key') || ''; |
|
|
this.messages = []; |
|
|
this.init(); |
|
|
} |
|
|
|
|
|
init() { |
|
|
this.bindEvents(); |
|
|
this.loadApiKey(); |
|
|
this.updateApiKeyDisplay(); |
|
|
} |
|
|
|
|
|
bindEvents() { |
|
|
document.getElementById('send-button').addEventListener('click', () => this.sendMessage()); |
|
|
document.getElementById('message-input').addEventListener('keypress', (e) => { |
|
|
if (e.key === 'Enter') this.sendMessage(); |
|
|
}); |
|
|
document.getElementById('save-api-key').addEventListener('click', () => this.saveApiKey()); |
|
|
} |
|
|
|
|
|
loadApiKey() { |
|
|
const savedKey = localStorage.getItem('openai-api-key'); |
|
|
if (savedKey) { |
|
|
this.apiKey = savedKey; |
|
|
document.getElementById('api-key-input').value = savedKey; |
|
|
} |
|
|
} |
|
|
|
|
|
saveApiKey() { |
|
|
const keyInput = document.getElementById('api-key-input'); |
|
|
if (keyInput.value.trim()) { |
|
|
this.apiKey = keyInput.value.trim(); |
|
|
localStorage.setItem('openai-api-key', this.apiKey); |
|
|
this.updateApiKeyDisplay(); |
|
|
this.showNotification('API key saved successfully!', 'success'); |
|
|
} else { |
|
|
this.showNotification('Please enter a valid API key', 'error'); |
|
|
} |
|
|
} |
|
|
|
|
|
updateApiKeyDisplay() { |
|
|
const keyInput = document.getElementById('api-key-input'); |
|
|
if (this.apiKey) { |
|
|
keyInput.placeholder = 'API key saved'; |
|
|
} else { |
|
|
keyInput.placeholder = 'Enter your OpenAI API key'; |
|
|
} |
|
|
} |
|
|
|
|
|
async sendMessage() { |
|
|
const input = document.getElementById('message-input'); |
|
|
const message = input.value.trim(); |
|
|
|
|
|
if (!message) return; |
|
|
|
|
|
if (!this.apiKey) { |
|
|
this.showNotification('Please enter your OpenAI API key first', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
this.addMessage(message, 'user'); |
|
|
input.value = ''; |
|
|
|
|
|
|
|
|
const typingIndicator = this.addTypingIndicator(); |
|
|
|
|
|
try { |
|
|
const response = await this.callOpenAI(message); |
|
|
|
|
|
typingIndicator.remove(); |
|
|
|
|
|
this.addMessage(response, 'assistant'); |
|
|
} catch (error) { |
|
|
|
|
|
typingIndicator.remove(); |
|
|
this.addMessage('Sorry, I encountered an error. Please try again.', 'assistant'); |
|
|
console.error('Error:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
addMessage(content, sender) { |
|
|
const messagesContainer = document.getElementById('messages'); |
|
|
const messageDiv = document.createElement('div'); |
|
|
messageDiv.className = `message ${sender}-message rounded-lg p-4 max-w-[80%] ${sender === 'user' ? 'ml-auto' : ''}`; |
|
|
|
|
|
if (sender === 'user') { |
|
|
messageDiv.innerHTML = ` |
|
|
<div class="font-bold text-blue-200 mb-1">You</div> |
|
|
<div>${content}</div> |
|
|
`; |
|
|
} else { |
|
|
messageDiv.innerHTML = ` |
|
|
<div class="font-bold text-blue-400 mb-1">Assistant</div> |
|
|
<div>${content}</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
messagesContainer.appendChild(messageDiv); |
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight; |
|
|
|
|
|
return messageDiv; |
|
|
} |
|
|
|
|
|
addTypingIndicator() { |
|
|
const messagesContainer = document.getElementById('messages'); |
|
|
const typingDiv = document.createElement('div'); |
|
|
typingDiv.className = 'message assistant-message rounded-lg p-4'; |
|
|
typingDiv.innerHTML = ` |
|
|
<div class="font-bold text-blue-400 mb-1">Assistant</div> |
|
|
<div class="flex space-x-1"> |
|
|
<div class="w-2 h-2 bg-blue-400 rounded-full animate-bounce"></div> |
|
|
<div class="w-2 h-2 bg-blue-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div> |
|
|
<div class="w-2 h-2 bg-blue-400 rounded-full animate-bounce" style="animation-delay: 0.4s"></div> |
|
|
</div> |
|
|
`; |
|
|
messagesContainer.appendChild(typingDiv); |
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight; |
|
|
return typingDiv; |
|
|
} |
|
|
|
|
|
async callOpenAI(message) { |
|
|
const response = await fetch('https://api.openai.com/v1/chat/completions', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
'Authorization': `Bearer ${this.apiKey}` |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
model: 'gpt-3.5-turbo', |
|
|
messages: [ |
|
|
{ role: 'system', content: 'You are a helpful assistant.' }, |
|
|
{ role: 'user', content: message } |
|
|
], |
|
|
temperature: 0.7 |
|
|
}) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
if (response.status === 401) { |
|
|
throw new Error('Invalid API key. Please check your API key and try again.'); |
|
|
} else if (response.status === 429) { |
|
|
throw new Error('Rate limit exceeded. Please wait before sending another request.'); |
|
|
} else { |
|
|
throw new Error(`API request failed with status ${response.status}`); |
|
|
} |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
return data.choices[0].message.content; |
|
|
} |
|
|
|
|
|
showNotification(message, type) { |
|
|
|
|
|
const existing = document.querySelector('.notification'); |
|
|
if (existing) existing.remove(); |
|
|
|
|
|
const notification = document.createElement('div'); |
|
|
notification.className = `notification fixed top-4 right-4 px-6 py-4 rounded-lg shadow-lg z-50 ${ |
|
|
type === 'success' ? 'bg-green-600' : 'bg-red-600' |
|
|
} text-white`; |
|
|
notification.textContent = message; |
|
|
|
|
|
document.body.appendChild(notification); |
|
|
|
|
|
setTimeout(() => { |
|
|
notification.remove(); |
|
|
}, 3000); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
window.chatApp = new ChatApp(); |
|
|
}); |