Spaces:
Running
Running
| // Shared JavaScript across all pages | |
| 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; | |
| } | |
| // Add user message to UI | |
| this.addMessage(message, 'user'); | |
| input.value = ''; | |
| // Show typing indicator | |
| const typingIndicator = this.addTypingIndicator(); | |
| try { | |
| const response = await this.callOpenAI(message); | |
| // Remove typing indicator | |
| typingIndicator.remove(); | |
| // Add assistant response | |
| this.addMessage(response, 'assistant'); | |
| } catch (error) { | |
| // Remove typing indicator | |
| 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) { | |
| // Remove existing notifications | |
| 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); | |
| } | |
| } | |
| // Initialize the app when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', () => { | |
| window.chatApp = new ChatApp(); | |
| }); |