| <!DOCTYPE html> |
| <html lang="en" data-bs-theme="dark"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>GAKR AI - Chat</title> |
| <link rel="stylesheet" href="https://cdn.replit.com/agent/bootstrap-agent-dark-theme.min.css"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> |
| <link rel="stylesheet" href="style.css"> |
| <style> |
| |
| :root { |
| |
| --gakr-blue: #4285F4; |
| --gakr-blue-dark: #1a73e8; |
| --gakr-blue-light: #e8f0fe; |
| --gakr-grey-text: #5f6368; |
| --gakr-grey-hover-bg: rgba(95, 99, 104, 0.1); |
| } |
| |
| :root[data-bs-theme="dark"] { |
| |
| --gakr-blue: #8ab4f8; |
| --gakr-blue-dark: #669df6; |
| --gakr-blue-light: rgba(138, 180, 248, 0.1); |
| --gakr-grey-text: #bdc1c6; |
| --gakr-grey-hover-bg: rgba(189, 193, 198, 0.1); |
| |
| } |
| |
| |
| .gakr-chat-layout { |
| display: flex; |
| flex-direction: column; |
| height: 100vh; |
| width: 100%; |
| background-color: var(--bs-body-bg); |
| color: var(--bs-body-color); |
| } |
| |
| .gakr-chat-header { |
| padding: 0.75rem 1.5rem; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| border-bottom: 1px solid var(--bs-border-color); |
| height: 64px; |
| } |
| |
| .gakr-logo-area { |
| display: flex; |
| align-items: center; |
| gap: 0.75rem; |
| } |
| |
| .gakr-brand-logo { |
| width: 24px; |
| height: 24px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .gakr-brand-text { |
| font-size: 1.25rem; |
| font-weight: 500; |
| color: var(--gakr-blue); |
| } |
| |
| .gakr-chat-wrapper { |
| flex: 1; |
| overflow: hidden; |
| position: relative; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| .gakr-chat-container { |
| flex: 1; |
| overflow-y: auto; |
| padding: 1rem; |
| display: flex; |
| flex-direction: column; |
| gap: 1.5rem; |
| width: 100%; |
| max-width: 800px; |
| margin: 0 auto; |
| } |
| |
| .gakr-prompt-area { |
| padding: 1rem; |
| border-top: 1px solid var(--bs-border-color); |
| width: 100%; |
| max-width: 800px; |
| margin: 0 auto; |
| |
| position: relative; |
| |
| } |
| |
| .gakr-message { |
| display: flex; |
| flex-direction: column; |
| gap: 0.5rem; |
| max-width: 90%; |
| } |
| |
| .gakr-message-user { |
| align-self: flex-end; |
| } |
| |
| .gakr-message-ai { |
| align-self: flex-start; |
| } |
| |
| .gakr-message-header { |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| font-size: 0.85rem; |
| color: var(--bs-secondary-color); |
| } |
| |
| .gakr-message-avatar { |
| width: 24px; |
| height: 24px; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 0.75rem; |
| } |
| |
| .gakr-message-content { |
| padding: 1rem; |
| border-radius: 12px; |
| line-height: 1.5; |
| |
| white-space: pre-wrap; |
| word-break: break-word; |
| } |
| |
| .gakr-message-user .gakr-message-content { |
| background-color: var(--bs-tertiary-bg); |
| border-top-right-radius: 4px; |
| } |
| |
| .gakr-message-ai .gakr-message-content { |
| background-color: rgba(66, 133, 244, 0.1); |
| border-top-left-radius: 4px; |
| } |
| |
| .gakr-message-user .gakr-message-avatar { |
| background-color: var(--bs-tertiary-bg); |
| } |
| |
| .gakr-message-ai .gakr-message-avatar { |
| background-color: rgba(66, 133, 244, 0.2); |
| color: var(--gakr-blue); |
| } |
| |
| |
| .gakr-input-container { |
| position: relative; |
| border-radius: 24px; |
| border: 1px solid var(--bs-border-color); |
| background: var(--bs-body-bg); |
| padding: 0.75rem 1rem; |
| display: flex; |
| |
| align-items: flex-end; |
| |
| box-shadow: 0 1px 5px rgba(0,0,0,0.1); |
| |
| transition: border-color 0.3s ease-in-out, box-shadow 0.3s ease-in-out; |
| flex-wrap: wrap; |
| gap: 0.5rem; |
| } |
| |
| |
| .gakr-input-container:focus-within { |
| border-color: var(--gakr-blue); |
| box-shadow: 0 1px 8px rgba(66, 133, 244, 0.2); |
| background: var(--bs-body-bg); |
| } |
| |
| |
| .gakr-input { |
| flex: 1; |
| border: none; |
| background: transparent; |
| |
| padding: 0; |
| |
| |
| outline: none; |
| color: var(--bs-body-color); |
| font-size: 1rem; |
| resize: none; |
| overflow-y: hidden; |
| line-height: 1.4; |
| |
| min-height: calc(1rem * 1.4); |
| max-height: calc(1rem * 1.4 * 5); |
| box-sizing: border-box; |
| white-space: pre-wrap; |
| word-break: break-word; |
| vertical-align: bottom; |
| transition: height 0.3s ease-in-out; |
| } |
| |
| .gakr-input:focus { |
| outline: none; |
| } |
| |
| |
| .gakr-input-actions { |
| display: flex; |
| |
| align-items: flex-end; |
| |
| padding-bottom: 0px; |
| |
| gap: 0.5rem; |
| font-size: 1.25rem; |
| flex-shrink: 0; |
| } |
| |
| |
| .gakr-action-button { |
| width: 36px; |
| height: 36px; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| color: var(--bs-secondary-color); |
| |
| transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; |
| } |
| |
| .gakr-action-button:hover { |
| background-color: var(--bs-tertiary-bg); |
| color: var(--bs-body-color); |
| } |
| |
| .gakr-action-button:active { |
| transform: scale(0.95); |
| background-color: var(--bs-tertiary-bg); |
| color: var(--bs-body-color); |
| transition: background-color 0s, transform 0.1s; |
| } |
| |
| .gakr-submit-button { |
| color: var(--gakr-blue); |
| } |
| |
| |
| .gakr-submit-button:hover { |
| background-color: var(--gakr-blue-light); |
| color: var(--gakr-blue-dark); |
| } |
| |
| .gakr-submit-button:active { |
| transform: scale(0.95); |
| background-color: var(--gakr-blue-light); |
| color: var(--gakr-blue-dark); |
| transition: background-color 0s, transform 0.1s; |
| } |
| |
| .gakr-submit-button.disabled { |
| opacity: 0.5; |
| cursor: default; |
| |
| transition: none; |
| } |
| |
| .gakr-submit-button.disabled:hover { |
| background-color: transparent; |
| color: var(--gakr-blue); |
| } |
| |
| |
| |
| .gakr-typing { |
| display: inline-flex; |
| align-items: center; |
| gap: 4px; |
| padding: 0.75rem 1rem; |
| border-radius: 12px; |
| background-color: rgba(66, 133, 244, 0.1); |
| margin-bottom: 1rem; |
| align-self: flex-end; |
| } |
| |
| .gakr-typing-dot { |
| width: 6px; |
| height: 6px; |
| border-radius: 50%; |
| background-color: var(--gakr-blue); |
| animation: typing 1.3s infinite ease-in-out; |
| } |
| |
| .gakr-typing-dot:nth-child(1) { animation-delay: 0s; } |
| .gakr-typing-dot:nth-child(2) { animation-delay: 0.2s; } |
| .gakr-typing-dot:nth-child(3) { animation-delay: 0.4s; } |
| |
| .gakr-thinking-toggle { |
| background: none; |
| border: none; |
| color: var(--gakr-blue); |
| cursor: pointer; |
| font-size: 0.9rem; |
| padding: 0; |
| margin-bottom: 0.5rem; |
| text-decoration: underline; |
| } |
| |
| .gakr-thinking-content { |
| display: block; |
| margin-top: 0.5rem; |
| padding: 0.5rem; |
| background-color: rgba(138, 180, 248, 0.05); |
| border-left: 3px solid var(--gakr-blue); |
| font-style: italic; |
| } |
| |
| .gakr-welcome { |
| text-align: center; |
| max-width: 600px; |
| margin: 4rem auto; |
| } |
| |
| .gakr-welcome-icon { |
| font-size: 3rem; |
| margin-bottom: 1.5rem; |
| color: var(--gakr-blue); |
| } |
| |
| .gakr-welcome-title { |
| font-size: 2rem; |
| margin-bottom: 1rem; |
| font-weight: 500; |
| } |
| |
| |
| .gakr-login-button { |
| color: var(--gakr-blue); |
| text-decoration: none; |
| background: transparent; |
| border: 1px solid var(--gakr-blue); |
| padding: 0.5rem 1rem; |
| border-radius: 50px; |
| font-size: 0.9rem; |
| transition: background-color 0.2s; |
| } |
| |
| .gakr-login-button:hover { |
| background-color: rgba(66, 133, 244, 0.1); |
| } |
| |
| |
| .gakr-profile-button { |
| color: var(--gakr-blue); |
| text-decoration: none; |
| background: transparent; |
| border: 1px solid var(--gakr-blue); |
| padding: 0.5rem 1rem; |
| border-radius: 50px; |
| font-size: 0.9rem; |
| transition: background-color 0.2s; |
| display: inline-flex; |
| align-items: center; |
| gap: 0.5rem; |
| } |
| |
| .gakr-profile-button:hover { |
| background-color: rgba(66, 133, 244, 0.1); |
| } |
| |
| |
| .gakr-login-prompt { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0, 0, 0, 0.5); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| z-index: 1000; |
| opacity: 0; |
| visibility: hidden; |
| transition: opacity 0.3s, visibility 0.3s; |
| } |
| |
| .gakr-login-prompt.show { |
| opacity: 1; |
| visibility: visible; |
| } |
| |
| .gakr-login-prompt-content { |
| background-color: var(--bs-body-bg); |
| border-radius: 12px; |
| padding: 1.5rem; |
| max-width: 400px; |
| width: 90%; |
| } |
| |
| .gakr-login-prompt-title { |
| font-size: 1.25rem; |
| font-weight: 500; |
| margin-bottom: 1rem; |
| color: var(--gakr-blue); |
| } |
| |
| .gakr-login-prompt-buttons { |
| display: flex; |
| flex-direction: column; |
| gap: 0.75rem; |
| margin-top: 1.5rem; |
| } |
| |
| .gakr-button-primary { |
| background-color: var(--gakr-blue); |
| color: white; |
| border: none; |
| padding: 0.75rem 1rem; |
| border-radius: 50px; |
| font-size: 0.9rem; |
| text-align: center; |
| text-decoration: none; |
| cursor: pointer; |
| transition: background-color 0.2s; |
| } |
| |
| .gakr-button-primary:hover { |
| background-color: #3b78e7; |
| } |
| |
| .gakr-button-secondary { |
| background-color: transparent; |
| color: var(--gakr-blue); |
| border: 1px solid var(--gakr-blue); |
| padding: 0.75rem 1rem; |
| border-radius: 50px; |
| font-size: 0.9rem; |
| text-align: center; |
| text-decoration: none; |
| cursor: pointer; |
| transition: background-color 0.2s; |
| } |
| |
| .gakr-button-secondary:hover { |
| background-color: rgba(66, 133, 244, 0.1); |
| } |
| |
| .gakr-button-text { |
| background-color: transparent; |
| color: var(--bs-body-color); |
| border: none; |
| padding: 0.75rem 1rem; |
| border-radius: 50px; |
| font-size: 0.9rem; |
| text-align: center; |
| text-decoration: none; |
| cursor: pointer; |
| transition: background-color 0.2s; |
| } |
| |
| .gakr-button-text:hover { |
| background-color: var(--bs-tertiary-bg); |
| } |
| |
| |
| .attachment-options-container { |
| position: absolute; |
| bottom: 100%; |
| left: 1rem; |
| transform: translateY(-10px); |
| z-index: 10; |
| |
| background-color: var(--bs-body-bg); |
| border: 1px solid var(--bs-border-color); |
| border-radius: 15px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); |
| padding: 15px; |
| |
| display: flex; |
| gap: 20px; |
| justify-content: flex-start; |
| min-width: max-content; |
| |
| opacity: 0; |
| visibility: hidden; |
| pointer-events: none; |
| |
| transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out, visibility 0s linear 0.3s; |
| } |
| |
| |
| .attachment-options-container.visible { |
| opacity: 1; |
| visibility: visible; |
| pointer-events: auto; |
| transform: translateY(-20px); |
| transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out, visibility 0s linear 0s; |
| } |
| |
| .attachment-option { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| cursor: pointer; |
| color: var(--bs-body-color); |
| font-size: 0.8rem; |
| gap: 5px; |
| transition: color 0.2s ease-in-out; |
| } |
| |
| .attachment-option:hover { |
| color: var(--gakr-blue); |
| } |
| |
| .attachment-icon { |
| width: 48px; |
| height: 48px; |
| border-radius: 50%; |
| background-color: var(--bs-tertiary-bg); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 1.2rem; |
| transition: background-color 0.2s ease-in-out; |
| } |
| |
| .attachment-option:hover .attachment-icon { |
| background-color: var(--gakr-blue-light); |
| } |
| |
| .attachment-text { |
| text-align: center; |
| } |
| |
| |
| .attached-file-preview-container { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 8px; |
| |
| width: 100%; |
| } |
| |
| .attached-file-chip { |
| display: flex; |
| align-items: center; |
| background-color: var(--bs-tertiary-bg); |
| border-radius: 16px; |
| padding: 4px 8px; |
| font-size: 0.85rem; |
| color: var(--bs-body-color); |
| max-width: 150px; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| position: relative; |
| } |
| |
| .attached-file-chip .file-name { |
| flex-grow: 1; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| padding-right: 8px; |
| } |
| |
| .attached-file-chip .remove-file { |
| cursor: pointer; |
| margin-left: 4px; |
| color: var(--bs-secondary-color); |
| } |
| |
| .attached-file-chip .remove-file:hover { |
| color: var(--bs-body-color); |
| } |
| |
| .attached-file-chip img { |
| width: 24px; |
| height: 24px; |
| border-radius: 4px; |
| object-fit: cover; |
| margin-right: 8px; |
| } |
| |
| |
| .hidden-file-input { |
| display: none; |
| } |
| |
| |
| |
| |
| </style> |
| </head> |
| <body> |
| <div class="gakr-chat-layout"> |
| <header class="gakr-chat-header"> |
| <div class="gakr-logo-area"> |
| <a href="/" class="gakr-brand-logo"> |
| <i class="fas fa-robot" style="color: var(--gakr-blue);"></i> |
| </a> |
| <a href="/" class="gakr-brand-text" style="text-decoration: none;">GAKR AI</a> |
| </div> |
|
|
| <div class="gakr-nav-controls"> |
| <a href="/auth" class="gakr-login-button">Sign in / Register</a> |
| </div> |
| </header> |
|
|
| <div class="gakr-chat-wrapper"> |
| <div class="gakr-chat-container" id="chatContainer"> |
| <div class="gakr-welcome" id="welcomeMessage"> |
| <div class="gakr-welcome-icon"> |
| <i class="fas fa-robot"></i> |
| </div> |
| <h1 class="gakr-welcome-title">How can I help you today?</h1> |
| <p>I'm GAKR AI, your AI assistant. Ask me anything!</p> |
| </div> |
|
|
| <div class="gakr-message gakr-message-ai d-none" id="initialMessage"> |
| <div class="gakr-message-header"> |
| <div class="gakr-message-avatar"> |
| <i class="fas fa-robot"></i> |
| </div> |
| <span>GAKR AI</span> |
| </div> |
| <div class="gakr-message-content">Hello! I'm GAKR AI How can I help you today? |
| </div> |
| </div> |
|
|
| <div class="gakr-typing d-none" id="typingIndicator"> |
| <div class="gakr-typing-dot"></div> |
| <div class="gakr-typing-dot"></div> |
| <div class="gakr-typing-dot"></div> |
| </div> |
| </div> |
|
|
| <div class="gakr-prompt-area"> |
| <div class="gakr-input-container" id="inputContainer"> |
| <div class="gakr-action-button" id="addButton"> |
| <i class="fas fa-plus"></i> |
| </div> |
| <div class="attached-file-preview-container" id="attachedFilePreviewContainer"> |
| </div> |
| <textarea class="gakr-input" id="userInput" placeholder="Message GAKR AI..." rows="1"></textarea> |
|
|
| <div class="gakr-input-actions"> |
| <div class="gakr-action-button"> |
| <i class="fas fa-microphone"></i> |
| </div> |
| <div class="gakr-action-button gakr-submit-button disabled" id="submitButton"> |
| <i class="fas fa-arrow-right"></i> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="attachment-options-container"> |
| <div class="attachment-option" id="cameraOption"> |
| <div class="attachment-icon"><i class="fas fa-camera"></i></div> |
| <div class="attachment-text">Camera</div> |
| <input type="file" id="cameraInput" class="hidden-file-input" accept="image/*" capture="camera"> |
| </div> |
| <div class="attachment-option" id="galleryOption"> |
| <div class="attachment-icon"><i class="fas fa-image"></i></div> |
| <div class="attachment-text">Gallery</div> |
| <input type="file" id="galleryInput" class="hidden-file-input" accept="image/*"> |
| </div> |
| <div class="attachment-option" id="filesOption"> |
| <div class="attachment-icon"><i class="fas fa-paperclip"></i></div> |
| <div class="attachment-text">Files</div> |
| <input type="file" id="filesInput" class="hidden-file-input" accept="*/*"> |
| </div> |
| <div class="attachment-option" id="driveOption"> |
| <div class="attachment-icon"><i class="fab fa-google-drive"></i></div> |
| <div class="attachment-text">Drive</div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="gakr-login-prompt" id="loginPrompt"> |
| <div class="gakr-login-prompt-content"> |
| <div class="gakr-login-prompt-title">Continue with GAKR AI</div> |
| <p>You've had 5 conversations with GAKR AI. Would you like to create an account to save your history?</p> |
| <div class="gakr-login-prompt-buttons"> |
| <a href="login.html" class="gakr-button-primary">Sign in / Register</a> |
| <a href="/login?signup=true" class="gakr-button-secondary">Create account</a> |
| <button type="button" class="gakr-button-text" id="continueGuest">Continue as guest</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| const chatContainer = document.getElementById('chatContainer'); |
| const userInput = document.getElementById('userInput'); |
| const submitButton = document.getElementById('submitButton'); |
| const typingIndicator = document.getElementById('typingIndicator'); |
| const welcomeMessage = document.getElementById('welcomeMessage'); |
| const initialMessage = document.getElementById('initialMessage'); |
| const loginPrompt = document.getElementById('loginPrompt'); |
| const continueGuest = document.getElementById('continueGuest'); |
| |
| const addButton = document.getElementById('addButton'); |
| const attachmentOptionsContainer = document.querySelector('.attachment-options-container'); |
| |
| const cameraInput = document.getElementById('cameraInput'); |
| const galleryInput = document.getElementById('galleryInput'); |
| const filesInput = document.getElementById('filesInput'); |
| const attachedFilePreviewContainer = document.getElementById('attachedFilePreviewContainer'); |
| const inputContainer = document.getElementById('inputContainer'); |
| |
| let messageCount = 0; |
| let attachedFiles = []; |
| |
| |
| const isLoggedIn = localStorage.getItem("isLoggedIn") === "true"; |
| |
| |
| updateHeader(); |
| |
| function updateHeader() { |
| const navControls = document.querySelector('.gakr-nav-controls'); |
| if (isLoggedIn) { |
| navControls.innerHTML = '<a href="/profile" class="gakr-profile-button"><i class="fas fa-user"></i> Profile</a>'; |
| } else { |
| navControls.innerHTML = '<a href="/auth" class="gakr-login-button">Sign in / Register</a>'; |
| } |
| } |
| |
| |
| const style = getComputedStyle(userInput); |
| const lineHeight = parseFloat(style.lineHeight); |
| const minHeight = lineHeight; |
| const maxHeight = lineHeight * 5; |
| |
| const initialInputContainerPaddingTop = parseFloat(getComputedStyle(inputContainer).paddingTop); |
| const initialInputContainerPaddingBottom = parseFloat(getComputedStyle(inputContainer).paddingBottom); |
| const initialActionButtonsHeight = document.querySelector('.gakr-input-actions').offsetHeight; |
| |
| function autoGrowTextarea() { |
| if (!userInput) { |
| console.error("autoGrowTextarea called but userInput element not found."); |
| return; |
| } |
| |
| userInput.style.height = 'auto'; |
| const scrollHeight = userInput.scrollHeight; |
| |
| let newTextareaHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight); |
| userInput.style.height = newTextareaHeight + 'px'; |
| |
| if (attachedFiles.length > 0) { |
| userInput.style.paddingTop = '0px'; |
| userInput.style.paddingBottom = '0px'; |
| inputContainer.style.alignItems = 'flex-start'; |
| } else { |
| userInput.style.paddingTop = '0px'; |
| userInput.style.paddingBottom = '0px'; |
| inputContainer.style.alignItems = 'flex-end'; |
| } |
| |
| if (scrollHeight > maxHeight) { |
| userInput.style.overflowY = 'auto'; |
| } else { |
| userInput.style.overflowY = 'hidden'; |
| } |
| |
| |
| if (userInput.value.trim().length > 0) { |
| submitButton.classList.remove('disabled'); |
| } else { |
| submitButton.classList.add('disabled'); |
| } |
| } |
| |
| userInput.addEventListener('input', autoGrowTextarea); |
| setTimeout(autoGrowTextarea, 0); |
| |
| |
| |
| |
| userInput.addEventListener('keydown', function(event) { |
| if (event.key === 'Enter' && !event.shiftKey && !submitButton.classList.contains('disabled')) { |
| event.preventDefault(); |
| sendMessage(); |
| } |
| }); |
| |
| |
| submitButton.addEventListener('click', function() { |
| if (!this.classList.contains('disabled')) { |
| sendMessage(); |
| } |
| }); |
| |
| |
| const microphoneButton = document.querySelector('.gakr-input-actions .gakr-action-button:first-child'); |
| if (microphoneButton) { |
| microphoneButton.addEventListener('click', function() { |
| alert('Voice input feature is currently in development.'); |
| }); |
| } |
| |
| |
| continueGuest.addEventListener('click', function() { |
| loginPrompt.classList.remove('show'); |
| }); |
| |
| |
| addButton.addEventListener('click', function(event) { |
| event.stopPropagation(); |
| attachmentOptionsContainer.classList.toggle('visible'); |
| }); |
| |
| |
| document.getElementById('cameraOption').addEventListener('click', function() { |
| cameraInput.click(); |
| attachmentOptionsContainer.classList.remove('visible'); |
| }); |
| |
| document.getElementById('galleryOption').addEventListener('click', function() { |
| galleryInput.click(); |
| attachmentOptionsContainer.classList.remove('visible'); |
| }); |
| |
| document.getElementById('filesOption').addEventListener('click', function() { |
| filesInput.click(); |
| attachmentOptionsContainer.classList.remove('visible'); |
| }); |
| |
| document.getElementById('driveOption').addEventListener('click', function() { |
| alert("Google Drive integration requires backend setup and Google API access."); |
| attachmentOptionsContainer.classList.remove('visible'); |
| }); |
| |
| |
| [cameraInput, galleryInput, filesInput].forEach(input => { |
| input.addEventListener('change', function(event) { |
| const files = event.target.files; |
| if (files.length > 0) { |
| for (let i = 0; i < files.length; i++) { |
| attachedFiles.push(files[i]); |
| addFileChip(files[i]); |
| } |
| autoGrowTextarea(); |
| userInput.focus(); |
| } |
| event.target.value = ''; |
| }); |
| }); |
| |
| function addFileChip(file) { |
| const chip = document.createElement('div'); |
| chip.className = 'attached-file-chip'; |
| chip.setAttribute('data-filename', file.name); |
| |
| let previewElement; |
| if (file.type.startsWith('image/')) { |
| const img = document.createElement('img'); |
| img.src = URL.createObjectURL(file); |
| img.alt = file.name; |
| img.onload = () => URL.revokeObjectURL(img.src); |
| previewElement = img; |
| } else { |
| const icon = document.createElement('i'); |
| icon.className = 'fas fa-paperclip'; |
| previewElement = icon; |
| } |
| |
| const fileNameSpan = document.createElement('span'); |
| fileNameSpan.className = 'file-name'; |
| fileNameSpan.textContent = file.name; |
| |
| const removeButton = document.createElement('span'); |
| removeButton.className = 'remove-file'; |
| removeButton.innerHTML = '×'; |
| removeButton.addEventListener('click', function() { |
| removeFileChip(file.name); |
| }); |
| |
| chip.appendChild(previewElement); |
| chip.appendChild(fileNameSpan); |
| chip.appendChild(removeButton); |
| |
| attachedFilePreviewContainer.appendChild(chip); |
| autoGrowTextarea(); |
| } |
| |
| function removeFileChip(filename) { |
| const chipToRemove = attachedFilePreviewContainer.querySelector(`[data-filename="${filename}"]`); |
| if (chipToRemove) { |
| attachedFilePreviewContainer.removeChild(chipToRemove); |
| attachedFiles = attachedFiles.filter(file => file.name !== filename); |
| autoGrowTextarea(); |
| } |
| } |
| |
| |
| document.addEventListener('click', function(event) { |
| const isClickInsideAddButton = addButton && addButton.contains(event.target); |
| const isClickInsideOptions = attachmentOptionsContainer && attachmentOptionsContainer.contains(event.target); |
| |
| if (attachmentOptionsContainer && attachmentOptionsContainer.classList.contains('visible') && !isClickInsideAddButton && !isClickInsideOptions) { |
| attachmentOptionsContainer.classList.remove('visible'); |
| } |
| }); |
| |
| if (attachmentOptionsContainer) { |
| attachmentOptionsContainer.addEventListener('click', function(event){ |
| event.stopPropagation(); |
| }); |
| } |
| |
| |
| let inlineLoginPromptShown = false; |
| function maybeShowInlineLoginPrompt() { |
| if (isLoggedIn) return; |
| if (inlineLoginPromptShown) return; |
| if (messageCount % 5 !== 0) return; |
| |
| inlineLoginPromptShown = true; |
| |
| const overlay = document.createElement('div'); |
| overlay.style.position = 'fixed'; |
| overlay.style.inset = '0'; |
| overlay.style.backgroundColor = 'rgba(0,0,0,0.35)'; |
| overlay.style.display = 'flex'; |
| overlay.style.alignItems = 'center'; |
| overlay.style.justifyContent = 'center'; |
| overlay.style.zIndex = '9999'; |
| |
| const card = document.createElement('div'); |
| card.style.width = '100%'; |
| card.style.maxWidth = '420px'; |
| card.style.borderRadius = '16px'; |
| card.style.backgroundColor = 'var(--bs-body-bg)'; |
| card.style.border = '1px solid var(--bs-border-color)'; |
| card.style.boxShadow = '0 16px 40px rgba(0,0,0,0.25)'; |
| card.style.padding = '1.25rem 1.5rem'; |
| card.style.display = 'flex'; |
| card.style.flexDirection = 'column'; |
| card.style.gap = '0.75rem'; |
| |
| const title = document.createElement('div'); |
| title.textContent = "Save your conversations"; |
| title.style.fontWeight = '600'; |
| title.style.fontSize = '1rem'; |
| |
| const text = document.createElement('div'); |
| text.textContent = "Log in to keep your chat history across devices. If you continue as a guest, your conversations may be lost later."; |
| text.style.fontSize = '0.9rem'; |
| text.style.opacity = '0.9'; |
| |
| const buttonRow = document.createElement('div'); |
| buttonRow.style.display = 'flex'; |
| buttonRow.style.gap = '0.75rem'; |
| buttonRow.style.marginTop = '0.5rem'; |
| |
| const skipBtn = document.createElement('button'); |
| skipBtn.textContent = "Continue without login"; |
| skipBtn.style.flex = '1'; |
| skipBtn.style.borderRadius = '999px'; |
| skipBtn.style.border = '1px solid var(--bs-border-color)'; |
| skipBtn.style.backgroundColor = 'transparent'; |
| skipBtn.style.color = 'var(--bs-body-color)'; |
| skipBtn.style.padding = '0.5rem 0.75rem'; |
| skipBtn.style.fontSize = '0.85rem'; |
| skipBtn.style.cursor = 'pointer'; |
| |
| const loginBtn = document.createElement('button'); |
| loginBtn.textContent = "Log in to save chats"; |
| loginBtn.style.flex = '1'; |
| loginBtn.style.borderRadius = '999px'; |
| loginBtn.style.border = 'none'; |
| loginBtn.style.backgroundColor = 'var(--gakr-blue)'; |
| loginBtn.style.color = '#fff'; |
| loginBtn.style.padding = '0.5rem 0.75rem'; |
| loginBtn.style.fontSize = '0.85rem'; |
| loginBtn.style.cursor = 'pointer'; |
| |
| skipBtn.addEventListener('click', function () { |
| overlay.remove(); |
| }); |
| |
| loginBtn.addEventListener('click', function () { |
| window.location.href = '/auth'; |
| }); |
| |
| buttonRow.appendChild(skipBtn); |
| buttonRow.appendChild(loginBtn); |
| card.appendChild(title); |
| card.appendChild(text); |
| card.appendChild(buttonRow); |
| overlay.appendChild(card); |
| document.body.appendChild(overlay); |
| } |
| |
| |
| async function sendMessage() { |
| const text = userInput.value.trim(); |
| |
| |
| if (!text) { |
| return; |
| } |
| |
| if (!welcomeMessage.classList.contains('d-none')) { |
| welcomeMessage.classList.add('d-none'); |
| } |
| |
| if (initialMessage.classList.contains('d-none')) { |
| initialMessage.classList.remove('d-none'); |
| } |
| |
| const filesSnapshot = [...attachedFiles]; |
| |
| let userMessageContent = text; |
| if (filesSnapshot.length > 0) { |
| const fileNames = filesSnapshot.map(f => f.name).join(', '); |
| userMessageContent += ` (Attached: ${fileNames})`; |
| } |
| addMessage(userMessageContent, 'user'); |
| messageCount++; |
| |
| userInput.value = ''; |
| attachedFiles = []; |
| attachedFilePreviewContainer.innerHTML = ''; |
| autoGrowTextarea(); |
| userInput.dispatchEvent(new Event('input', { bubbles: true })); |
| |
| typingIndicator.classList.remove('d-none'); |
| scrollToBottom(); |
| |
| const formData = new FormData(); |
| |
| formData.append('prompt', text); |
| |
| if (filesSnapshot.length > 0) { |
| filesSnapshot.forEach((file) => { |
| formData.append('files', file); |
| }); |
| } |
| |
| try { |
| const response = await fetch('/api/analyze', { |
| method: 'POST', |
| body: formData |
| }); |
| |
| if (!response.ok) { |
| typingIndicator.classList.add('d-none'); |
| addMessage('There was a server issue. Please try again later.', 'ai'); |
| return; |
| } |
| |
| |
| const aiMessageDiv = addMessage('', 'ai', true); |
| let accumulatedText = ''; |
| |
| const reader = response.body.getReader(); |
| const decoder = new TextDecoder(); |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| const chunk = decoder.decode(value, { stream: true }); |
| accumulatedText += chunk; |
| updateAIMessage(aiMessageDiv, accumulatedText); |
| } |
| |
| if (messageCount > 0 && messageCount % 5 === 0) { |
| maybeShowInlineLoginPrompt(); |
| } |
| } catch (error) { |
| console.error('Fetch Error:', error); |
| typingIndicator.classList.add('d-none'); |
| addMessage('There was a server issue. Please try again later.', 'ai'); |
| } finally { |
| if (typingIndicator) { |
| typingIndicator.classList.add('d-none'); |
| } |
| scrollToBottom(); |
| } |
| } |
| |
| function addMessage(text, type, streaming = false) { |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `gakr-message gakr-message-${type}`; |
| |
| const headerDiv = document.createElement('div'); |
| headerDiv.className = 'gakr-message-header'; |
| |
| const avatarDiv = document.createElement('div'); |
| avatarDiv.className = 'gakr-message-avatar'; |
| |
| const avatarIcon = document.createElement('i'); |
| avatarIcon.className = type === 'user' ? 'fas fa-user' : 'fas fa-robot'; |
| |
| const nameSpan = document.createElement('span'); |
| nameSpan.textContent = type === 'user' ? 'You' : 'GAKR AI'; |
| |
| const contentDiv = document.createElement('div'); |
| contentDiv.className = 'gakr-message-content'; |
| if (!streaming) { |
| if (type === 'ai') { |
| updateAIMessage(contentDiv, text); |
| } else { |
| contentDiv.textContent = text; |
| } |
| } |
| |
| avatarDiv.appendChild(avatarIcon); |
| headerDiv.appendChild(avatarDiv); |
| headerDiv.appendChild(nameSpan); |
| |
| messageDiv.appendChild(headerDiv); |
| messageDiv.appendChild(contentDiv); |
| |
| chatContainer.appendChild(messageDiv); |
| |
| if (!streaming) { |
| scrollToBottom(); |
| } |
| |
| return streaming ? contentDiv : null; |
| } |
| |
| function updateAIMessage(contentDiv, text) { |
| |
| contentDiv.innerHTML = ''; |
| |
| const thinkStart = text.indexOf('<think>'); |
| const thinkEnd = text.indexOf('</think>'); |
| if (thinkStart !== -1 && thinkEnd !== -1 && thinkEnd > thinkStart) { |
| const thinking = text.substring(thinkStart + 7, thinkEnd).trim(); |
| const solution = text.substring(thinkEnd + 8).trim(); |
| |
| |
| const toggleButton = document.createElement('button'); |
| toggleButton.className = 'gakr-thinking-toggle'; |
| toggleButton.textContent = 'hide thinking'; |
| toggleButton.onclick = function() { |
| const thinkingDiv = this.nextElementSibling; |
| if (thinkingDiv.style.display === 'none') { |
| thinkingDiv.style.display = 'block'; |
| this.textContent = 'hide thinking'; |
| } else { |
| thinkingDiv.style.display = 'none'; |
| this.textContent = 'show thinking'; |
| } |
| }; |
| |
| |
| const thinkingDiv = document.createElement('div'); |
| thinkingDiv.className = 'gakr-thinking-content'; |
| thinkingDiv.textContent = thinking; |
| |
| contentDiv.appendChild(toggleButton); |
| contentDiv.appendChild(thinkingDiv); |
| contentDiv.appendChild(document.createTextNode(solution)); |
| } else { |
| contentDiv.textContent = text; |
| } |
| } |
| |
| function scrollToBottom() { |
| setTimeout(() => { |
| if (chatContainer) { |
| chatContainer.scrollTop = chatContainer.scrollHeight; |
| } |
| }, 0); |
| } |
| |
| |
| const urlParams = new URLSearchParams(window.location.search); |
| const initialQuery = urlParams.get('q'); |
| |
| if (initialQuery) { |
| userInput.value = initialQuery; |
| userInput.dispatchEvent(new Event('input', { bubbles: true })); |
| |
| setTimeout(function() { |
| if (submitButton && !submitButton.classList.contains('disabled')) { |
| submitButton.click(); |
| } |
| }, 100); |
| } else { |
| if (userInput) { |
| if (userInput.value.trim().length === 0 && attachedFiles.length === 0) { |
| if (submitButton) submitButton.classList.add('disabled'); |
| } else { |
| if (submitButton) submitButton.classList.remove('disabled'); |
| } |
| autoGrowTextarea(); |
| } |
| } |
| }); |
| </script> |
|
|
|
|
|
|
| </body> |
| </html> |
|
|