| |
| 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(); |
| }); |