Download index.html from Opera8/Hooshvare: direct link, hf CLI and curl.
- Browser
- Download file 14.6 kB
-
https://huggingface.co/spaces/Opera8/Hooshvare/resolve/main/index.html
- Command line
-
hf download hf://spaces/Opera8/Hooshvare/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Opera8/Hooshvare/resolve/main/index.html
14.6 kB
| <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); | |
| /* استفاده از dvh برای حل مشکل نوار آدرس در موبایل */ | |
| 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; /* جلوگیری از استایلهای پیشفرض iOS */ | |
| } | |
| .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; | |
| // فوکوس مجدد در موبایل کیبورد را باز میکند که ممکن است آزاردهنده باشد | |
| // userInput.focus(); | |
| } | |
| 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> |