| <!DOCTYPE html> |
| <html lang="fa" dir="rtl"> |
| <head> |
| <meta charset="UTF-8"> |
| |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
| <title>Google AI Chatbot</title> |
| <style> |
| :root { |
| --bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); |
| --glass-bg: rgba(30, 41, 59, 0.85); |
| --glass-border: rgba(255, 255, 255, 0.08); |
| --text-main: #f8fafc; |
| --accent-color: #38bdf8; |
| --user-bubble: linear-gradient(135deg, #0284c7 0%, #0369a1 100%); |
| --ai-bubble: rgba(255, 255, 255, 0.08); |
| } |
| |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Tahoma, sans-serif; |
| margin: 0; |
| padding: 0; |
| background: var(--bg-gradient); |
| color: var(--text-main); |
| |
| height: 100dvh; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| overflow: hidden; |
| } |
| |
| .chat-container { |
| width: 100%; |
| max-width: 850px; |
| height: 100%; |
| background: var(--glass-bg); |
| display: flex; |
| flex-direction: column; |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); |
| } |
| |
| |
| @media (min-width: 768px) { |
| .chat-container { |
| height: 90vh; |
| border-radius: 24px; |
| border: 1px solid var(--glass-border); |
| } |
| } |
| |
| .chat-header { |
| padding: 15px 20px; |
| background: rgba(15, 23, 42, 0.6); |
| border-bottom: 1px solid var(--glass-border); |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| } |
| |
| .chat-header .status-dot { |
| width: 10px; |
| height: 10px; |
| background: #4ade80; |
| border-radius: 50%; |
| box-shadow: 0 0 10px #4ade80; |
| } |
| |
| .chat-header h2 { |
| margin: 0; |
| font-size: 16px; |
| font-weight: 600; |
| color: var(--accent-color); |
| } |
| |
| .messages-box { |
| flex: 1; |
| padding: 20px; |
| overflow-y: auto; |
| display: flex; |
| flex-direction: column; |
| gap: 16px; |
| } |
| |
| .messages-box::-webkit-scrollbar { width: 4px; } |
| .messages-box::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 10px; } |
| |
| .message { |
| max-width: 85%; |
| padding: 12px 16px; |
| border-radius: 18px; |
| font-size: 14px; |
| line-height: 1.6; |
| word-wrap: break-word; |
| animation: fadeIn 0.3s ease forwards; |
| } |
| |
| .message.user { |
| background: var(--user-bubble); |
| align-self: flex-start; |
| border-bottom-right-radius: 4px; |
| } |
| |
| .message.ai { |
| background: var(--ai-bubble); |
| border: 1px solid var(--glass-border); |
| align-self: flex-end; |
| border-bottom-left-radius: 4px; |
| white-space: pre-wrap; |
| } |
| |
| .input-wrapper { |
| background: rgba(15, 23, 42, 0.8); |
| border-top: 1px solid var(--glass-border); |
| padding: 12px 15px; |
| display: flex; |
| flex-direction: column; |
| gap: 10px; |
| |
| padding-bottom: env(safe-area-inset-bottom, 12px); |
| } |
| |
| .preview-container { |
| display: none; |
| align-items: center; |
| gap: 10px; |
| background: rgba(255, 255, 255, 0.1); |
| padding: 6px 10px; |
| border-radius: 10px; |
| width: fit-content; |
| } |
| |
| .preview-container img { |
| width: 45px; |
| height: 45px; |
| object-fit: cover; |
| border-radius: 6px; |
| } |
| |
| .preview-container .remove-btn { |
| color: #ef4444; |
| cursor: pointer; |
| font-weight: bold; |
| font-size: 12px; |
| padding: 5px; |
| } |
| |
| .input-area { |
| display: flex; |
| gap: 8px; |
| align-items: stretch; |
| } |
| |
| .upload-label { |
| background: rgba(255, 255, 255, 0.08); |
| border: 1px solid var(--glass-border); |
| border-radius: 14px; |
| padding: 0 14px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| min-height: 48px; |
| } |
| |
| .upload-label svg { |
| width: 24px; |
| height: 24px; |
| fill: var(--text-main); |
| } |
| |
| .input-area input[type="text"] { |
| flex: 1; |
| background: rgba(255, 255, 255, 0.05); |
| border: 1px solid var(--glass-border); |
| padding: 0 15px; |
| border-radius: 14px; |
| color: white; |
| font-size: 15px; |
| outline: none; |
| min-height: 48px; |
| -webkit-appearance: none; |
| } |
| |
| .input-area input[type="text"]:focus { |
| border-color: var(--accent-color); |
| } |
| |
| .input-area button { |
| background: var(--accent-color); |
| color: #0f172a; |
| border: none; |
| padding: 0 20px; |
| border-radius: 14px; |
| font-size: 15px; |
| font-weight: bold; |
| cursor: pointer; |
| min-height: 48px; |
| } |
| |
| .input-area button:disabled { |
| background: #64748b; |
| color: #1e293b; |
| } |
| |
| .typing-indicator { |
| display: flex; |
| gap: 4px; |
| padding: 12px 16px; |
| background: var(--ai-bubble); |
| border-radius: 12px; |
| align-self: flex-end; |
| border: 1px solid var(--glass-border); |
| } |
| |
| .typing-indicator span { |
| width: 6px; |
| height: 6px; |
| background: var(--accent-color); |
| border-radius: 50%; |
| animation: bounce 1.4s infinite ease-in-out both; |
| } |
| .typing-indicator span:nth-child(1) { animation-delay: -0.32s; } |
| .typing-indicator span:nth-child(2) { animation-delay: -0.16s; } |
| |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } |
| @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1.0); } } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="chat-container"> |
| <div class="chat-header"> |
| <div class="status-dot"></div> |
| <h2>دستیار هوشمند گوگل</h2> |
| </div> |
| <div class="messages-box" id="messagesBox"> |
| <div class="message ai">سلام! من متصل به هوشواره گوگل هستم. پیام یا تصویر خود را بفرستید.</div> |
| </div> |
| |
| <div class="input-wrapper"> |
| <div class="preview-container" id="previewContainer"> |
| <img src="" id="imagePreview"> |
| <span class="remove-btn" onclick="removeSelectedImage()">✕ حذف</span> |
| </div> |
| |
| <div class="input-area"> |
| <input type="file" id="fileInput" accept="image/*" style="display: none;" onchange="previewImage(event)"> |
| <label for="fileInput" class="upload-label" title="آپلود تصویر"> |
| <svg viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.66 1.34 3 3 3s3-1.34 3-3V5c0-2.48-2.02-4.5-4.5-4.5S7 2.52 7 5v12.5c0 3.59 2.91 6.5 6.5 6.5s6.5-2.91 6.5-6V6h-1.5z"/></svg> |
| </label> |
| |
| <input type="text" id="userInput" placeholder="پیام خود را بنویسید..." onkeypress="handleKeyPress(event)"> |
| <button id="sendBtn" onclick="sendMessage()">ارسال</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| const messagesBox = document.getElementById('messagesBox'); |
| const userInput = document.getElementById('userInput'); |
| const fileInput = document.getElementById('fileInput'); |
| const sendBtn = document.getElementById('sendBtn'); |
| const previewContainer = document.getElementById('previewContainer'); |
| const imagePreview = document.getElementById('imagePreview'); |
| |
| function handleKeyPress(e) { |
| if (e.key === 'Enter') sendMessage(); |
| } |
| |
| function previewImage(event) { |
| const file = event.target.files[0]; |
| if (file) { |
| const reader = new FileReader(); |
| reader.onload = function(e) { |
| imagePreview.src = e.target.result; |
| previewContainer.style.display = 'flex'; |
| } |
| reader.readAsDataURL(file); |
| } |
| } |
| |
| function removeSelectedImage() { |
| fileInput.value = ''; |
| previewContainer.style.display = 'none'; |
| imagePreview.src = ''; |
| } |
| |
| async function sendMessage() { |
| const text = userInput.value.trim(); |
| const file = fileInput.files[0]; |
| if (!text && !file) return; |
| |
| if (file) appendImageMessage(imagePreview.src, 'user'); |
| if (text) appendMessage(text, 'user'); |
| |
| userInput.value = ''; |
| removeSelectedImage(); |
| |
| userInput.disabled = true; |
| sendBtn.disabled = true; |
| const indicator = document.createElement('div'); |
| indicator.classList.add('typing-indicator'); |
| indicator.id = 'typingIndicator'; |
| indicator.innerHTML = '<span></span><span></span><span></span>'; |
| messagesBox.appendChild(indicator); |
| messagesBox.scrollTop = messagesBox.scrollHeight; |
| |
| const formData = new FormData(); |
| formData.append('message', text || "این تصویر را تحلیل کن"); |
| if (file) formData.append('image', file); |
| |
| try { |
| const response = await fetch('/api/chat', { method: 'POST', body: formData }); |
| |
| if (!response.ok) { |
| document.getElementById('typingIndicator')?.remove(); |
| appendMessage("خطا: مشکلی در دریافت پاسخ رخ داد.", 'ai'); |
| resetInputs(); |
| return; |
| } |
| |
| const reader = response.body.getReader(); |
| const decoder = new TextDecoder('utf-8'); |
| let done = false; |
| let buffer = ''; |
| let isFirstChunk = true; |
| let aiMessageDiv = null; |
| |
| while (!done) { |
| const { value, done: readerDone } = await reader.read(); |
| done = readerDone; |
| if (value) { |
| if (isFirstChunk) { |
| document.getElementById('typingIndicator')?.remove(); |
| aiMessageDiv = appendMessage("", 'ai'); |
| isFirstChunk = false; |
| } |
| |
| buffer += decoder.decode(value, { stream: true }); |
| const lines = buffer.split('\n'); |
| buffer = lines.pop(); |
| |
| for (let line of lines) { |
| if (line.trim().startsWith('data:')) { |
| try { |
| const jsonStr = line.substring(line.indexOf('data:') + 5).trim(); |
| if (jsonStr) { |
| const data = JSON.parse(jsonStr); |
| aiMessageDiv.innerText = data.text; |
| messagesBox.scrollTop = messagesBox.scrollHeight; |
| } |
| } catch (e) { |
| |
| } |
| } |
| } |
| } |
| } |
| |
| if (isFirstChunk) { |
| document.getElementById('typingIndicator')?.remove(); |
| appendMessage("متأسفانه پاسخی دریافت نشد.", 'ai'); |
| } |
| |
| } catch (error) { |
| document.getElementById('typingIndicator')?.remove(); |
| appendMessage("خطای شبکه: دریافت پاسخ از سرور انجام نشد.", 'ai'); |
| } finally { |
| resetInputs(); |
| } |
| } |
| |
| function resetInputs() { |
| userInput.disabled = false; |
| sendBtn.disabled = false; |
| |
| |
| } |
| |
| function appendMessage(text, sender) { |
| const msgDiv = document.createElement('div'); |
| msgDiv.classList.add('message', sender); |
| msgDiv.innerText = text; |
| messagesBox.appendChild(msgDiv); |
| messagesBox.scrollTop = messagesBox.scrollHeight; |
| return msgDiv; |
| } |
| |
| function appendImageMessage(src, sender) { |
| const msgDiv = document.createElement('div'); |
| msgDiv.classList.add('message', sender); |
| msgDiv.style.padding = '5px'; |
| const img = document.createElement('img'); |
| img.src = src; |
| img.style.maxWidth = '100%'; |
| img.style.borderRadius = '14px'; |
| img.style.display = 'block'; |
| msgDiv.appendChild(img); |
| messagesBox.appendChild(msgDiv); |
| messagesBox.scrollTop = messagesBox.scrollHeight; |
| } |
| </script> |
| </body> |
| </html> |