| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Athspi AI Chat</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> |
| <style> |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| html, body { |
| height: 100%; |
| font-family: 'Inter', sans-serif; |
| color: #1f1f1f; |
| } |
| |
| body { |
| background: linear-gradient(180deg, #fdfdff 0%, #f7f7f9 100%); |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| } |
| |
| |
| .chat-container { |
| display: flex; |
| flex-direction: column; |
| height: 100%; |
| width: 100%; |
| max-width: 800px; |
| margin: 0 auto; |
| position: relative; |
| } |
| |
| |
| .chat-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 20px 24px; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| background: rgba(253, 253, 255, 0.8); |
| backdrop-filter: blur(10px); |
| z-index: 10; |
| } |
| |
| .icon-btn { |
| background: none; |
| border: none; |
| cursor: pointer; |
| padding: 8px; |
| color: #333; |
| } |
| |
| .icon-btn svg { |
| width: 24px; |
| height: 24px; |
| stroke-width: 2; |
| } |
| |
| .icon-refresh svg { |
| stroke-width: 2.5; |
| } |
| |
| |
| .chat-messages { |
| flex-grow: 1; |
| padding: 100px 24px 20px 24px; |
| overflow-y: auto; |
| display: flex; |
| flex-direction: column; |
| gap: 16px; |
| } |
| |
| |
| .welcome-container { |
| flex-grow: 1; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| text-align: center; |
| } |
| |
| .welcome-message { |
| font-size: 2.5rem; |
| font-weight: 600; |
| color: #202124; |
| } |
| |
| |
| .message { |
| max-width: 85%; |
| padding: 12px 18px; |
| border-radius: 20px; |
| line-height: 1.6; |
| word-wrap: break-word; |
| } |
| |
| .user-message { |
| background-color: #e0eaff; |
| color: #1f1f1f; |
| align-self: flex-end; |
| border-bottom-right-radius: 5px; |
| } |
| |
| .model-message { |
| background-color: #f1f3f4; |
| color: #3c4043; |
| align-self: flex-start; |
| border-bottom-left-radius: 5px; |
| } |
| |
| |
| .model-message p { margin-bottom: 1rem; } |
| .model-message p:last-child { margin-bottom: 0; } |
| .model-message ul, .model-message ol { padding-left: 20px; margin-bottom: 1rem; } |
| .model-message pre.code-block { |
| background-color: #282c34; |
| color: #abb2bf; |
| padding: 1rem; |
| border-radius: 8px; |
| overflow-x: auto; |
| font-family: 'Courier New', Courier, monospace; |
| font-size: 0.9em; |
| } |
| .model-message code { font-family: 'Courier New', Courier, monospace; } |
| |
| |
| .link-container { |
| background-color: #ffffff; |
| border: 1px solid #e0e0e0; |
| border-radius: 12px; |
| padding: 15px; |
| margin-top: 10px; |
| } |
| .link-container p { |
| margin-bottom: 8px !important; |
| word-break: break-all; |
| font-size: 0.9rem; |
| } |
| .link-container p:last-child { margin-bottom: 0 !important; } |
| .link-container a { color: #1a73e8; text-decoration: none; } |
| .link-container a:hover { text-decoration: underline; } |
| |
| |
| .audio-player { |
| width: 100%; |
| margin-top: 12px; |
| } |
| |
| |
| .loading-indicator { |
| display: flex; |
| align-items: center; |
| gap: 5px; |
| align-self: flex-start; |
| } |
| .loading-indicator span { |
| width: 8px; |
| height: 8px; |
| background-color: #9e9e9e; |
| border-radius: 50%; |
| animation: bounce 1.4s infinite ease-in-out both; |
| } |
| .loading-indicator .dot1 { animation-delay: -0.32s; } |
| .loading-indicator .dot2 { animation-delay: -0.16s; } |
| @keyframes bounce { |
| 0%, 80%, 100% { transform: scale(0); } |
| 40% { transform: scale(1.0); } |
| } |
| |
| |
| .chat-input-area { |
| padding: 16px 24px 32px 24px; |
| background: transparent; |
| } |
| |
| .input-wrapper { |
| display: flex; |
| align-items: center; |
| background-color: #ffffff; |
| border-radius: 50px; |
| padding: 8px 8px 8px 20px; |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.07); |
| border: 1px solid #e0e0e0; |
| } |
| |
| .text-input { |
| flex-grow: 1; |
| border: none; |
| outline: none; |
| font-size: 1rem; |
| font-family: 'Inter', sans-serif; |
| background-color: transparent; |
| padding-right: 15px; |
| } |
| |
| #send-btn { |
| background-color: #4285F4; |
| border: none; |
| border-radius: 50%; |
| width: 40px; |
| height: 40px; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| cursor: pointer; |
| transition: background-color 0.2s; |
| } |
| #send-btn:hover { background-color: #3367d6; } |
| #send-btn svg { color: white; } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="chat-container"> |
| |
| <header class="chat-header"> |
| <button class="icon-btn" aria-label="Menu"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"> |
| <line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line> |
| </svg> |
| </button> |
| <button id="new-chat-btn" class="icon-btn icon-refresh" aria-label="Start new chat"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"> |
| <path d="M2.5 12a9.5 9.5 0 1 0 5.4-8.4M2.5 4v5.5h5.5"/> |
| </svg> |
| </button> |
| </header> |
|
|
| <main class="chat-messages" id="chat-messages"> |
| <div class="welcome-container" id="welcome-container"> |
| <h1 class="welcome-message">What can I help with?</h1> |
| </div> |
| </main> |
|
|
| <footer class="chat-input-area"> |
| <div class="input-wrapper"> |
| <input type="text" id="text-input" class="text-input" placeholder="Ask anything..."> |
| <button id="send-btn" aria-label="Send message"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> |
| </button> |
| </div> |
| </footer> |
|
|
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| const CHAT_URL = '/chat'; |
| const NEW_CHAT_URL = '/new-chat'; |
| |
| |
| const chatMessages = document.getElementById('chat-messages'); |
| const textInput = document.getElementById('text-input'); |
| const sendBtn = document.getElementById('send-btn'); |
| const welcomeContainer = document.getElementById('welcome-container'); |
| const newChatBtn = document.getElementById('new-chat-btn'); |
| |
| const addMessage = (sender, messageHtml) => { |
| const messageDiv = document.createElement('div'); |
| messageDiv.classList.add('message', `${sender}-message`); |
| messageDiv.innerHTML = messageHtml; |
| chatMessages.appendChild(messageDiv); |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| return messageDiv; |
| }; |
| |
| const showLoading = () => { |
| const loadingDiv = document.createElement('div'); |
| loadingDiv.id = 'loading-indicator'; |
| loadingDiv.classList.add('message', 'model-message', 'loading-indicator'); |
| loadingDiv.innerHTML = `<span class="dot1"></span><span class="dot2"></span><span class="dot3"></span>`; |
| chatMessages.appendChild(loadingDiv); |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| }; |
| |
| const hideLoading = () => { |
| const loadingDiv = document.getElementById('loading-indicator'); |
| if (loadingDiv) loadingDiv.remove(); |
| }; |
| |
| const handleSendMessage = async () => { |
| const prompt = textInput.value.trim(); |
| if (!prompt) return; |
| |
| if (welcomeContainer) welcomeContainer.style.display = 'none'; |
| |
| addMessage('user', prompt); |
| textInput.value = ''; |
| showLoading(); |
| |
| try { |
| const response = await fetch(CHAT_URL, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ message: prompt }) |
| }); |
| |
| hideLoading(); |
| |
| if (!response.ok) { |
| throw new Error(`Server error: ${response.status}`); |
| } |
| |
| const data = await response.json(); |
| |
| if (data.error) { |
| addMessage('model', `Error: ${data.error}`); |
| return; |
| } |
| |
| const modelMessageDiv = addMessage('model', data.response_html); |
| |
| if (data.has_audio && data.audio_filename) { |
| const audioPlayer = document.createElement('audio'); |
| audioPlayer.controls = true; |
| audioPlayer.classList.add('audio-player'); |
| audioPlayer.src = `/download/${data.audio_filename}`; |
| modelMessageDiv.appendChild(audioPlayer); |
| } |
| |
| } catch (error) { |
| hideLoading(); |
| console.error('Error:', error); |
| addMessage('model', `Something went wrong. Please check the connection to the server. ${error.message}`); |
| } |
| }; |
| |
| const startNewChat = async () => { |
| try { |
| await fetch(NEW_CHAT_URL, { method: 'POST' }); |
| chatMessages.innerHTML = ''; |
| chatMessages.appendChild(welcomeContainer); |
| welcomeContainer.style.display = 'flex'; |
| } catch(error) { |
| console.error('Failed to start new chat:', error); |
| alert('Could not start a new session. Please check the server connection.'); |
| } |
| } |
| |
| |
| sendBtn.addEventListener('click', handleSendMessage); |
| textInput.addEventListener('keydown', (event) => { |
| if (event.key === 'Enter') { |
| event.preventDefault(); |
| handleSendMessage(); |
| } |
| }); |
| newChatBtn.addEventListener('click', startNewChat); |
| }); |
| </script> |
|
|
| </body> |
| </html> |