New Conversation

0 tok/s
0 tokens
$0.00

Welcome to Neural Chat

Experience the power of GPT-5 with advanced conversation threading and real-time analytics

Press ⌘+Enter to send
0 characters
} init() { // Modal handlers document.getElementById('tokenBtn').addEventListener('click', () => this.showTokenModal()); document.getElementById('cancelToken').addEventListener('click', () => this.hideTokenModal()); document.getElementById('saveToken').addEventListener('click', () => this.saveToken()); // Chat handlers document.getElementById('newChatBtn').addEventListener('click', () => this.createNewThread()); document.getElementById('sendBtn').addEventListener('click', () => this.sendMessage()); document.getElementById('clearChat').addEventListener('click', () => this.clearCurrentChat()); document.getElementById('exportChat').addEventListener('click', () => this.exportChat()); // Input handlers const messageInput = document.getElementById('messageInput'); messageInput.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'Enter') { this.sendMessage(); } }); messageInput.addEventListener('input', () => this.updateInputCounter()); // Initial setup this.createNewThread(); } showTokenModal() { document.getElementById('tokenModal').classList.remove('hidden'); document.getElementById('tokenModal').classList.add('flex'); } hideTokenModal() { document.getElementById('tokenModal').classList.add('hidden'); document.getElementById('tokenModal').classList.remove('flex'); } saveToken() { const tokenInput = document.getElementById('tokenInput').value.trim(); if (!tokenInput) return; try { this.tokenData = JSON.parse(tokenInput); this.accessToken = this.tokenData.accessToken; localStorage.setItem('tokenData', tokenInput); this.updateTokenStatus(); this.hideTokenModal(); // Clear input document.getElementById('tokenInput').value = ''; this.showNotification('Token configured successfully!', 'success'); } catch (error) { this.showNotification('Invalid JSON format', 'error'); } } updateTokenStatus() { if (!this.tokenData) return; const expiryDate = new Date(this.tokenData.expires); const now = new Date(); const timeLeft = expiryDate - now; document.getElementById('tokenStatus').textContent = this.tokenData.user.email; document.getElementById('tokenExpiry').textContent = expiryDate.toLocaleString(); if (timeLeft > 0) { const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24)); const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); document.getElementById('timeLeft').textContent = `${days}d ${hours}h ${minutes}m`; document.getElementById('timeLeft').className = 'text-xs text-green-400'; // Update token meter (percentage of time left) const totalDuration = new Date(this.tokenData.expires) - new Date(this.tokenData.user.iat * 1000); const remainingPercentage = (timeLeft / totalDuration) * 100; document.getElementById('tokenMeter').style.width = `${Math.max(0, remainingPercentage)}%`; } else { document.getElementById('timeLeft').textContent = 'EXPIRED'; document.getElementById('timeLeft').className = 'text-xs text-red-400'; document.getElementById('tokenMeter').style.width = '0%'; } } startTokenTimer() { setInterval(() => { this.updateTokenStatus(); }, 60000); // Update every minute } createNewThread() { const threadId = Date.now().toString(); const thread = { id: threadId, title: 'New Conversation', messages: [], createdAt: new Date(), tokenCount: 0 }; this.threads.set(threadId, thread); this.currentThread = threadId; this.updateThreadsList(); this.updateChatDisplay(); this.saveToStorage(); } updateThreadsList() { const container = document.getElementById('chatThreads'); container.innerHTML = ''; for (const [id, thread] of this.threads) { const isActive = id === this.currentThread; const div = document.createElement('div'); div.className = `flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors cursor-pointer slide-in ${ isActive ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }`; div.innerHTML = ` chat_bubble ${thread.title} ${thread.messages.length} `; div.addEventListener('click', () => this.switchThread(id)); container.appendChild(div); } } switchThread(threadId) { this.currentThread = threadId; this.updateThreadsList(); this.updateChatDisplay(); } updateChatDisplay() { const thread = this.threads.get(this.currentThread); if (!thread) return; document.getElementById('chatTitle').textContent = thread.title; const container = document.getElementById('messagesContainer'); container.innerHTML = ''; if (thread.messages.length === 0) { container.innerHTML = `
psychology

GPT-5 AI Assistant

Start a conversation to begin

`; return; } thread.messages.forEach(message => { this.addMessageToDisplay(message); }); // Scroll to bottom container.scrollTop = container.scrollHeight; } addMessageToDisplay(message) { const container = document.getElementById('messagesContainer'); const messageDiv = document.createElement('div'); messageDiv.className = `fade-in-up chat-bubble ${message.role === 'user' ? 'ml-12' : 'mr-12'}`; const isUser = message.role === 'user'; const bgColor = isUser ? 'bg-indigo-600' : 'bg-[#1C1C27]'; const alignment = isUser ? 'ml-auto' : ''; messageDiv.innerHTML = `
${isUser ? 'person' : 'psychology'}
${isUser ? 'You' : 'GPT-5'}
${this.formatMessage(message.content)}
${message.timestamp ? `
${new Date(message.timestamp).toLocaleTimeString()}
` : ''}
`; container.appendChild(messageDiv); container.scrollTop = container.scrollHeight; } formatMessage(content) { // Basic markdown-like formatting return content .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/`(.*?)`/g, '$1') .replace(/\n/g, '
'); } async sendMessage() { if (!this.accessToken) { this.showNotification('Please configure your API token first', 'error'); return; } const input = document.getElementById('messageInput'); const message = input.value.trim(); if (!message || this.isStreaming) return; // Add user message const userMessage = { role: 'user', content: message, timestamp: new Date() }; const thread = this.threads.get(this.currentThread); thread.messages.push(userMessage); // Update thread title if it's the first message if (thread.messages.length === 1) { thread.title = message.substring(0, 30) + (message.length > 30 ? '...' : ''); this.updateThreadsList(); } this.addMessageToDisplay(userMessage); input.value = ''; this.updateInputCounter(); // Prepare AI response const aiMessage = { role: 'assistant', content: '', timestamp: new Date() }; thread.messages.push(aiMessage); this.addMessageToDisplay(aiMessage); // Send to API await this.streamResponse(thread.messages, aiMessage); this.saveToStorage(); } async streamResponse(messages, aiMessage) { this.isStreaming = true; const sendBtn = document.getElementById('sendBtn'); sendBtn.disabled = true; try { const response = await fetch('https://boots-content-pack-reactions.trycloudflare.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.accessToken}` }, body: JSON.stringify({ model: 'gpt-5', messages: messages.map(m => ({ role: m.role, content: m.content })), stream: true }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; let tokenCount = 0; let startTime = Date.now(); while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.trim() === '') continue; if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { this.isStreaming = false; sendBtn.disabled = false; return; } try { const parsed = JSON.parse(data); const content = parsed.choices?.[0]?.delta?.content; if (content) { aiMessage.content += content; tokenCount++; // Update display this.updateStreamingMessage(aiMessage); // Update metrics const elapsed = (Date.now() - startTime) / 1000; this.tokenSpeed = Math.round(tokenCount / elapsed); this.totalTokens += 1; this.totalCost += 0.00002; // Rough estimate this.updateMetrics(); } } catch (e) { console.warn('Failed to parse:', data); } } } } } catch (error) { console.error('Stream error:', error); aiMessage.content = 'Sorry, I encountered an error while processing your request.'; this.updateStreamingMessage(aiMessage); this.showNotification('Connection error', 'error'); } finally { this.isStreaming = false; sendBtn.disabled = false; } } updateStreamingMessage(message) { const container = document.getElementById('messagesContainer'); const messages = container.querySelectorAll('.fade-in-up'); const lastMessage = messages[messages.length - 1]; if (lastMessage) { const contentDiv = lastMessage.querySelector('.text-white.text-sm.leading-relaxed'); if (contentDiv) { contentDiv.innerHTML = this.formatMessage(message.content) + '|'; } } container.scrollTop = container.scrollHeight; } updateMetrics() { document.getElementById('tokenSpeed').textContent = `Speed: ${this.tokenSpeed} tok/s`; document.getElementById('tokenUsage').textContent = `Usage: ${this.totalTokens} tokens`; document.getElementById('costEstimate').textContent = `Cost: $${this.totalCost.toFixed(4)}`; } updateInputCounter() { const input = document.getElementById('messageInput'); const counter = document.getElementById('inputCounter'); counter.textContent = `${input.value.length} characters`; } clearCurrentChat() { const thread = this.threads.get(this.currentThread); if (thread) { thread.messages = []; thread.title = 'New Conversation'; this.updateThreadsList(); this.updateChatDisplay(); this.saveToStorage(); } } exportChat() { const thread = this.threads.get(this.currentThread); if (!thread || thread.messages.length === 0) return; const exportData = { title: thread.title, createdAt: thread.createdAt, messages: thread.messages }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `chat-${thread.title.replace(/[^a-zA-Z0-9]/g, '-')}.json`; a.click(); URL.revokeObjectURL(url); } showNotification(message, type = 'info') { // Simple notification system const notification = document.createElement('div'); notification.className = `fixed top-4 right-4 p-4 rounded-lg text-white z-50 fade-in-up ${ type === 'success' ? 'bg-green-600' : type === 'error' ? 'bg-red-600' : 'bg-blue-600' }`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } saveToStorage() { const data = { threads: Array.from(this.threads.entries()), currentThread: this.currentThread, totalTokens: this.totalTokens, totalCost: this.totalCost }; localStorage.setItem('chatAppData', JSON.stringify(data)); } loadFromStorage() { // Load token data const tokenData = localStorage.getItem('tokenData'); if (tokenData) { try { this.tokenData = JSON.parse(tokenData); this.accessToken = this.tokenData.accessToken; this.updateTokenStatus(); } catch (e) { console.warn('Failed to load token data'); } } // Load chat data const chatData = localStorage.getItem('chatAppData'); if (chatData) { try { const data = JSON.parse(chatData); this.threads = new Map(data.threads); this.currentThread = data.currentThread; this.totalTokens = data.totalTokens || 0; this.totalCost = data.totalCost || 0; if (this.threads.size > 0) { this.updateThreadsList(); this.updateChatDisplay(); this.updateMetrics(); } } catch (e) { console.warn('Failed to load chat data'); } } } } // Initialize the app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { const app = new NeuralChatApp(); // Add CSS for fade-out animation const style = document.createElement('style'); style.textContent = ` @keyframes fade-out { 0% { opacity: 1; transform: scale(1); } 100% { opacity: 0; transform: scale(0.95); } } `; document.head.appendChild(style); // Add global error handler window.addEventListener('error', (e) => { console.error('Global error:', e.error); app.showNotification('An unexpected error occurred', 'error'); }); // Add visibility change handler to pause/resume timers document.addEventListener('visibilitychange', () => { if (!document.hidden) { app.updateTokenStatus(); } }); });