Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AJ - AI Assistant by AJ STUDIOZ</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 20px; | |
| } | |
| .container { | |
| background: white; | |
| border-radius: 20px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.3); | |
| max-width: 800px; | |
| width: 100%; | |
| overflow: hidden; | |
| } | |
| .header { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 30px; | |
| text-align: center; | |
| } | |
| .header h1 { | |
| font-size: 2.5em; | |
| margin-bottom: 10px; | |
| } | |
| .header p { | |
| opacity: 0.9; | |
| font-size: 1.1em; | |
| } | |
| .url-setup { | |
| background: #fff3cd; | |
| padding: 20px; | |
| border-bottom: 2px solid #ffc107; | |
| } | |
| .url-setup-label { | |
| font-size: 12px; | |
| font-weight: bold; | |
| color: #856404; | |
| margin-bottom: 10px; | |
| display: block; | |
| } | |
| .url-input-group { | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| } | |
| .url-setup input { | |
| flex: 1; | |
| min-width: 250px; | |
| padding: 10px 15px; | |
| border: 1px solid #ffc107; | |
| border-radius: 5px; | |
| font-size: 13px; | |
| font-family: 'Courier New', monospace; | |
| } | |
| .url-setup button { | |
| padding: 10px 20px; | |
| background: #ffc107; | |
| border: none; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| font-weight: bold; | |
| transition: 0.3s; | |
| white-space: nowrap; | |
| } | |
| .url-setup button:hover { | |
| background: #e0a800; | |
| } | |
| .url-status { | |
| font-size: 12px; | |
| padding: 8px 12px; | |
| text-align: center; | |
| background: #fff; | |
| border-radius: 5px; | |
| border: 1px solid #ddd; | |
| min-width: 180px; | |
| } | |
| .url-status.connected { | |
| background: #d4edda; | |
| color: #155724; | |
| border: 1px solid #c3e6cb; | |
| } | |
| .url-status.error { | |
| background: #f8d7da; | |
| color: #721c24; | |
| border: 1px solid #f5c6cb; | |
| } | |
| .chat-container { | |
| padding: 30px; | |
| height: 400px; | |
| overflow-y: auto; | |
| background: #f8f9fa; | |
| } | |
| .message { | |
| margin-bottom: 20px; | |
| display: flex; | |
| align-items: flex-start; | |
| } | |
| .message.user { | |
| justify-content: flex-end; | |
| } | |
| .message-content { | |
| max-width: 70%; | |
| padding: 15px 20px; | |
| border-radius: 15px; | |
| word-wrap: break-word; | |
| } | |
| .message.user .message-content { | |
| background: #667eea; | |
| color: white; | |
| border-bottom-right-radius: 5px; | |
| } | |
| .message.ai .message-content { | |
| background: white; | |
| color: #333; | |
| border-bottom-left-radius: 5px; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.1); | |
| } | |
| .input-container { | |
| padding: 20px; | |
| background: white; | |
| border-top: 2px solid #e9ecef; | |
| display: flex; | |
| gap: 10px; | |
| } | |
| #messageInput { | |
| flex: 1; | |
| padding: 15px; | |
| border: 2px solid #e9ecef; | |
| border-radius: 25px; | |
| font-size: 16px; | |
| outline: none; | |
| } | |
| #messageInput:focus { | |
| border-color: #667eea; | |
| } | |
| #sendBtn { | |
| padding: 15px 30px; | |
| background: #667eea; | |
| color: white; | |
| border: none; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| font-weight: bold; | |
| transition: 0.3s; | |
| min-width: 100px; | |
| } | |
| #sendBtn:hover:not(:disabled) { | |
| background: #764ba2; | |
| transform: scale(1.05); | |
| } | |
| #sendBtn:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| } | |
| .error-message { | |
| color: #d32f2f; | |
| padding: 15px; | |
| background: #ffebee; | |
| border-radius: 5px; | |
| margin-bottom: 15px; | |
| } | |
| .loader { | |
| display: inline-block; | |
| width: 8px; | |
| height: 8px; | |
| background: #667eea; | |
| border-radius: 50%; | |
| margin-left: 5px; | |
| animation: bounce 0.6s infinite; | |
| } | |
| @keyframes bounce { | |
| 0%, 100% { transform: translateY(0); } | |
| 50% { transform: translateY(-10px); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>🤖 AJ</h1> | |
| <p>Perfect AI Model - Powered by aj-mini</p> | |
| <p style="font-size: 0.9em; margin-top: 10px;">By AJ STUDIOZ</p> | |
| </div> | |
| <div class="url-setup"> | |
| <label class="url-setup-label">📡 ngrok API URL Configuration</label> | |
| <div class="url-input-group"> | |
| <input | |
| type="text" | |
| id="ngrokUrl" | |
| placeholder="https://xxxxx-xxxxx-xxxxx.ngrok.io" | |
| title="Enter your ngrok public URL from http://localhost:4040" | |
| > | |
| <button onclick="saveNgrokUrl()">Save URL</button> | |
| <div class="url-status" id="urlStatus">Not connected</div> | |
| </div> | |
| <div style="font-size: 11px; color: #856404; margin-top: 10px;"> | |
| 💡 Get your ngrok URL from: <code style="background: white; padding: 2px 5px; border-radius: 3px;">http://localhost:4040</code> | |
| </div> | |
| </div> | |
| <div id="errorContainer"></div> | |
| <div class="chat-container" id="chatContainer"> | |
| <div style="text-align: center; color: #999; padding: 20px;"> | |
| 👋 Start by entering your ngrok URL above, then send a message! | |
| </div> | |
| </div> | |
| <div class="input-container"> | |
| <input | |
| type="text" | |
| id="messageInput" | |
| placeholder="Ask AJ something..." | |
| autocomplete="off" | |
| > | |
| <button id="sendBtn" onclick="sendMessage()">Send</button> | |
| </div> | |
| </div> | |
| <script> | |
| let API_BASE_URL = localStorage.getItem('ngrokUrl') || ''; | |
| function saveNgrokUrl() { | |
| const url = document.getElementById('ngrokUrl').value.trim(); | |
| const statusEl = document.getElementById('urlStatus'); | |
| if (!url) { | |
| statusEl.textContent = '❌ URL required'; | |
| statusEl.className = 'url-status error'; | |
| return; | |
| } | |
| if (!url.startsWith('http')) { | |
| statusEl.textContent = '❌ Invalid URL (must start with http/https)'; | |
| statusEl.className = 'url-status error'; | |
| return; | |
| } | |
| statusEl.textContent = '⏳ Testing...'; | |
| statusEl.className = 'url-status'; | |
| fetch(url + '/', { method: 'GET', mode: 'cors' }) | |
| .then(resp => { | |
| if (resp.ok) { | |
| API_BASE_URL = url; | |
| localStorage.setItem('ngrokUrl', url); | |
| statusEl.textContent = '✅ Connected!'; | |
| statusEl.className = 'url-status connected'; | |
| clearError(); | |
| document.getElementById('chatContainer').innerHTML = '<div style="text-align: center; color: #999; padding: 20px;">💬 Connected! Send a message to AJ...</div>'; | |
| } else { | |
| throw new Error('Server responded with error'); | |
| } | |
| }) | |
| .catch(err => { | |
| statusEl.textContent = '❌ Connection failed'; | |
| statusEl.className = 'url-status error'; | |
| showError('Failed to connect to API. Check URL and try again.'); | |
| }); | |
| } | |
| function showError(msg) { | |
| const container = document.getElementById('errorContainer'); | |
| container.innerHTML = '<div class="error-message">⚠️ ' + msg + '</div>'; | |
| } | |
| function clearError() { | |
| document.getElementById('errorContainer').innerHTML = ''; | |
| } | |
| function addMessage(text, isUser = false) { | |
| const chatContainer = document.getElementById('chatContainer'); | |
| if (chatContainer.innerHTML.includes('Start by entering')) { | |
| chatContainer.innerHTML = ''; | |
| } | |
| const messageEl = document.createElement('div'); | |
| messageEl.className = 'message ' + (isUser ? 'user' : 'ai'); | |
| const contentEl = document.createElement('div'); | |
| contentEl.className = 'message-content'; | |
| contentEl.textContent = text; | |
| messageEl.appendChild(contentEl); | |
| chatContainer.appendChild(messageEl); | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| } | |
| function sendMessage() { | |
| if (!API_BASE_URL) { | |
| showError('Please enter and save your ngrok URL first!'); | |
| return; | |
| } | |
| const input = document.getElementById('messageInput'); | |
| const message = input.value.trim(); | |
| if (!message) return; | |
| addMessage(message, true); | |
| input.value = ''; | |
| const sendBtn = document.getElementById('sendBtn'); | |
| sendBtn.disabled = true; | |
| sendBtn.textContent = 'Sending' + '<span class="loader"></span>'; | |
| fetch(API_BASE_URL + '/api/chat', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ message: message }), | |
| mode: 'cors' | |
| }) | |
| .then(resp => { | |
| if (!resp.ok) throw new Error('API error: ' + resp.status); | |
| return resp.json(); | |
| }) | |
| .then(data => { | |
| if (data.response) { | |
| addMessage(data.response, false); | |
| clearError(); | |
| } else if (data.error) { | |
| showError('API Error: ' + data.error); | |
| addMessage('Error: ' + data.error, false); | |
| } | |
| }) | |
| .catch(err => { | |
| showError('Failed to get response: ' + err.message); | |
| addMessage('Error: ' + err.message, false); | |
| }) | |
| .finally(() => { | |
| sendBtn.disabled = false; | |
| sendBtn.textContent = 'Send'; | |
| document.getElementById('messageInput').focus(); | |
| }); | |
| } | |
| document.getElementById('messageInput').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') sendMessage(); | |
| }); | |
| document.getElementById('ngrokUrl').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') saveNgrokUrl(); | |
| }); | |
| if (API_BASE_URL) { | |
| document.getElementById('ngrokUrl').value = API_BASE_URL; | |
| saveNgrokUrl(); | |
| } | |
| window.addEventListener('load', () => { | |
| document.getElementById('messageInput').focus(); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |