runflare / templates /chat-gemma-4-e4b-it.html
Sada8888's picture
Update templates/chat-gemma-4-e4b-it.html
a437711 verified
Raw
History Blame Contribute Delete
14.7 kB
<!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>دستیار هوشمند Gemma-4</title>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root { --bg-color: #E5DDD5; --chat-bg: #E5DDD5; --header-bg: #075E54; --user-msg: #DCF8C6; --bot-msg: #FFFFFF; --text-main: #303030; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body, html { font-family: 'Vazirmatn', sans-serif; height: 100%; background-color: var(--bg-color); overflow: hidden; }
.chat-container { display: flex; flex-direction: column; height: 100vh; height: 100dvh; max-width: 800px; margin: 0 auto; background: var(--chat-bg); box-shadow: 0 0 20px rgba(0,0,0,0.1); }
.chat-header { background: var(--header-bg); color: white; padding: 15px 20px; display: flex; align-items: center; gap: 15px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); z-index: 10; }
.avatar { width: 45px; height: 45px; background: #fff; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 24px; }
.header-info h2 { font-size: 1.1rem; font-weight: 700; margin-bottom: 2px; }
.header-info p { font-size: 0.8rem; color: #d1d1d1; }
.chat-box { flex-grow: 1; padding: 20px 15px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; background-image: url('https://www.transparenttextures.com/patterns/cubes.png'); }
.message-row { display: flex; flex-direction: column; max-width: 85%; animation: fadeIn 0.3s ease; }
.message-row.user { align-self: flex-start; }
.message-row.bot { align-self: flex-end; }
.message-bubble { padding: 12px 15px; border-radius: 18px; font-size: 0.95rem; line-height: 1.6; color: var(--text-main); position: relative; word-wrap: break-word; }
.message-row.user .message-bubble { background: var(--user-msg); border-top-right-radius: 4px; box-shadow: 1px 1px 3px rgba(0,0,0,0.08); }
.message-row.bot .message-bubble { background: var(--bot-msg); border-top-left-radius: 4px; box-shadow: -1px 1px 3px rgba(0,0,0,0.08); }
.file-preview-in-chat { max-width: 100%; border-radius: 12px; margin-bottom: 8px; }
.typing-indicator { display: none; padding: 12px 15px; background: var(--bot-msg); border-radius: 18px; border-top-left-radius: 4px; align-self: flex-end; box-shadow: -1px 1px 3px rgba(0,0,0,0.08); margin-top: 5px;}
.typing-indicator span { display: inline-block; width: 6px; height: 6px; background-color: #90949C; border-radius: 50%; margin: 0 2px; animation: bounce 1.3s linear infinite; }
.typing-indicator span:nth-child(2) { animation-delay: -1.1s; }
.typing-indicator span:nth-child(3) { animation-delay: -0.9s; }
.input-area { background: #f0f0f0; padding: 10px 15px; display: flex; flex-direction: column; z-index: 10; }
.attachment-preview { display: none; align-items: center; background: white; padding: 8px 12px; border-radius: 12px; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); gap: 10px;}
.attachment-preview img { width: 40px; height: 40px; border-radius: 6px; object-fit: cover; }
.attachment-preview .file-info { flex-grow: 1; font-size: 0.85rem; color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.remove-file { background: #ff4d4d; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; display: flex; justify-content: center; align-items: center; font-size: 14px;}
.input-wrapper { display: flex; align-items: flex-end; gap: 10px; }
.attach-btn { background: transparent; border: none; color: #888; font-size: 24px; cursor: pointer; padding: 5px; transition: color 0.3s; display: flex; align-items: center;}
.attach-btn:hover { color: var(--header-bg); }
#file-input { display: none; }
textarea { flex-grow: 1; border: none; border-radius: 20px; padding: 12px 15px; font-family: 'Vazirmatn', sans-serif; font-size: 0.95rem; resize: none; max-height: 120px; overflow-y: auto; outline: none; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.send-btn { background: var(--header-bg); color: white; border: none; border-radius: 50%; width: 45px; height: 45px; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: transform 0.2s, background 0.3s; box-shadow: 0 2px 5px rgba(0,0,0,0.2); flex-shrink: 0;}
.send-btn:hover { background: #054c44; transform: scale(1.05); }
.send-btn:disabled { background: #a5c0bd; cursor: not-allowed; transform: none; }
.send-btn svg { width: 20px; height: 20px; transform: translateX(-2px); }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes bounce { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-5px); } }
.message-bubble b, .message-bubble strong { font-weight: 700; }
.message-bubble code { background: #f4f4f4; padding: 2px 5px; border-radius: 4px; font-family: monospace; font-size: 0.9rem; direction: ltr; display: inline-block; white-space: pre-wrap; word-wrap: break-word; max-width: 100%;}
</style>
</head>
<body>
<div class="chat-container">
<header class="chat-header">
<div class="avatar">🤖</div>
<div class="header-info">
<h2>Gemma-4 (پرو)</h2>
<p id="status-text">آماده پاسخگویی به شما</p>
</div>
</header>
<div class="chat-box" id="chat-box">
<div class="message-row bot">
<div class="message-bubble">
سلام! من دستیار هوشمند Gemma هستم. می‌تونی سوالاتت رو بپرسی یا عکس و فایل صوتی بفرستی تا بررسی کنم. 😊
</div>
</div>
</div>
<div class="typing-indicator" id="typing-indicator">
<span></span><span></span><span></span>
</div>
<div class="input-area">
<div class="attachment-preview" id="attachment-preview">
<img src="" alt="preview" id="preview-img" style="display:none;">
<div id="file-icon" style="font-size: 24px; display:none;">📄</div>
<div class="file-info" id="file-name">نام فایل</div>
<button class="remove-file" id="remove-file-btn"></button>
</div>
<div class="input-wrapper">
<button class="attach-btn" id="attach-btn" title="ارسال عکس یا فایل">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
</button>
<input type="file" id="file-input" accept="image/*,audio/*,video/*">
<textarea id="text-input" rows="1" placeholder="پیام خود را بنویسید..."></textarea>
<button class="send-btn" id="send-btn">
<svg xmlns="http://www.w3.org/2000/svg" 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>
</div>
</div>
<script>
const chatBox = document.getElementById('chat-box');
const textInput = document.getElementById('text-input');
const attachBtn = document.getElementById('attach-btn');
const fileInput = document.getElementById('file-input');
const sendBtn = document.getElementById('send-btn');
const typingIndicator = document.getElementById('typing-indicator');
const statusText = document.getElementById('status-text');
const attachmentPreview = document.getElementById('attachment-preview');
const previewImg = document.getElementById('preview-img');
const fileIcon = document.getElementById('file-icon');
const fileNameDisplay = document.getElementById('file-name');
const removeFileBtn = document.getElementById('remove-file-btn');
let currentFile = null;
textInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight < 120 ? this.scrollHeight : 120) + 'px';
});
attachBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', function() {
const file = this.files[0];
if (file) {
currentFile = file;
fileNameDisplay.textContent = file.name;
attachmentPreview.style.display = 'flex';
if (file.type.startsWith('image/')) {
previewImg.src = URL.createObjectURL(file);
previewImg.style.display = 'block';
fileIcon.style.display = 'none';
} else {
previewImg.style.display = 'none';
fileIcon.style.display = 'block';
}
textInput.focus();
}
});
removeFileBtn.addEventListener('click', () => {
currentFile = null;
fileInput.value = '';
attachmentPreview.style.display = 'none';
if (previewImg.src) URL.revokeObjectURL(previewImg.src);
});
function appendMessage(text, isUser, fileUrl = null, isImage = false) {
const row = document.createElement('div');
row.className = `message-row ${isUser ? 'user' : 'bot'}`;
let mediaHtml = '';
if (fileUrl) {
if (isImage) mediaHtml = `<img src="${fileUrl}" class="file-preview-in-chat"><br>`;
else mediaHtml = `<div style="background:rgba(0,0,0,0.1); padding:8px; border-radius:8px; margin-bottom:8px;">📎 فایل ضمیمه شد</div>`;
}
const formattedText = text.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>').replace(/\n/g, '<br>');
row.innerHTML = `<div class="message-bubble">${mediaHtml}${formattedText}</div>`;
chatBox.appendChild(row);
chatBox.scrollTop = chatBox.scrollHeight;
return row.querySelector('.message-bubble');
}
sendBtn.addEventListener('click', async () => {
const text = textInput.value.trim();
if (!text && !currentFile) return;
let fileUrlForUI = null;
let isImage = false;
if (currentFile) {
fileUrlForUI = URL.createObjectURL(currentFile);
isImage = currentFile.type.startsWith('image/');
}
appendMessage(text, true, fileUrlForUI, isImage);
const fileToSend = currentFile;
const textToSend = text;
textInput.value = '';
textInput.style.height = 'auto';
removeFileBtn.click();
sendBtn.disabled = true;
textInput.disabled = true;
attachBtn.disabled = true;
typingIndicator.style.display = 'block';
chatBox.scrollTop = chatBox.scrollHeight;
statusText.textContent = "در حال اتصال به هوش مصنوعی...";
try {
const formData = new FormData();
formData.append('prompt', textToSend);
if (fileToSend) formData.append('file', fileToSend);
const response = await fetch('/api/chat', { method: 'POST', body: formData });
const data = await response.json();
if (data.status === 'success') {
const botBubble = appendMessage('', false);
let isFirstChunk = true;
const eventSource = new EventSource(`/api/stream_chat/${data.run_id}`);
eventSource.onmessage = function(event) {
if (event.data === "DONE") {
eventSource.close();
statusText.textContent = "آماده پاسخگویی به شما";
sendBtn.disabled = false; textInput.disabled = false; attachBtn.disabled = false;
textInput.focus();
} else {
if (isFirstChunk) {
typingIndicator.style.display = 'none';
statusText.textContent = "در حال نوشتن...";
isFirstChunk = false;
}
try {
const parsed = JSON.parse(event.data);
const formattedText = parsed.text.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>').replace(/\n/g, '<br>');
botBubble.innerHTML = formattedText;
chatBox.scrollTop = chatBox.scrollHeight;
} catch (e) {}
}
};
eventSource.onerror = function() {
eventSource.close();
typingIndicator.style.display = 'none';
statusText.textContent = "آماده پاسخگویی به شما";
// بررسی اینکه اگر کادر خالی بود خطای قطع شدن بنویسد
if (botBubble.innerHTML === '' || botBubble.innerHTML === '<br>') {
botBubble.innerHTML = "⚠️ خطای شبکه: اتصال استریم با سرور قطع شد.";
} else {
botBubble.innerHTML += "<br><br>⚠️ <i>(ارتباط استریم با سرور قطع شد)</i>";
}
sendBtn.disabled = false; textInput.disabled = false; attachBtn.disabled = false;
};
} else {
throw new Error(data.message || "خطای ناشناخته از سمت سرور");
}
} catch (error) {
typingIndicator.style.display = 'none';
statusText.textContent = "آماده پاسخگویی به شما";
appendMessage(`⚠️ خطا: ${error.message}`, false);
sendBtn.disabled = false; textInput.disabled = false; attachBtn.disabled = false;
}
});
textInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendBtn.click(); }
});
</script>
</body>
</html>