tts10 / Chat12b.html
Opera8's picture
Create Chat12b.html
aeaaaf8 verified
Raw
History Blame Contribute Delete
15.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>
<style>
:root {
--bg-color: #0b0f19;
--chat-bg: #151c2c;
--text-color: #f3f4f6;
--primary-color: #3b82f6;
--primary-hover: #2563eb;
--user-msg: #2563eb;
--bot-msg: #1f293d;
--border-color: #374151;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: Tahoma, Geneva, sans-serif;
-webkit-tap-highlight-color: transparent;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
flex-direction: column;
height: 100vh;
height: -webkit-fill-available;
overflow: hidden;
}
html {
height: -webkit-fill-available;
}
header {
background-color: var(--chat-bg);
padding: 15px;
text-align: center;
border-bottom: 1px solid var(--border-color);
font-size: 1.1rem;
font-weight: bold;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
#chat-container {
flex: 1;
overflow-y: auto;
padding: 15px;
display: flex;
flex-direction: column;
gap: 15px;
background-color: var(--bg-color);
}
.message {
max-width: 85%;
padding: 12px 16px;
border-radius: 18px;
line-height: 1.6;
font-size: 0.95rem;
word-wrap: break-word;
white-space: pre-wrap;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.user-message {
background-color: var(--user-msg);
color: #ffffff;
align-self: flex-start;
border-bottom-right-radius: 4px;
}
.bot-message {
background-color: var(--bot-msg);
color: var(--text-color);
align-self: flex-end;
border-bottom-left-radius: 4px;
border: 1px solid var(--border-color);
}
.media-preview {
max-width: 100%;
max-height: 200px;
border-radius: 8px;
margin-top: 8px;
display: block;
}
#input-container {
background-color: var(--chat-bg);
padding: 12px;
border-top: 1px solid var(--border-color);
display: flex;
flex-direction: column;
gap: 8px;
}
.input-row {
display: flex;
gap: 8px;
align-items: center;
}
textarea {
flex: 1;
background-color: var(--bg-color);
border: 1px solid var(--border-color);
color: var(--text-color);
padding: 12px;
border-radius: 24px;
resize: none;
height: 48px;
max-height: 100px;
font-size: 0.95rem;
outline: none;
direction: rtl;
}
textarea:focus {
border-color: var(--primary-color);
}
.btn {
background-color: var(--primary-color);
color: white;
border: none;
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.2s;
flex-shrink: 0;
}
.btn:hover {
background-color: var(--primary-hover);
}
.btn:disabled {
background-color: #4b5563;
cursor: not-allowed;
}
.file-label {
background-color: #374151;
color: white;
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
}
#file-input {
display: none;
}
#attachment-bar {
display: none;
background-color: #1f293d;
padding: 6px 12px;
border-radius: 8px;
font-size: 0.85rem;
align-items: center;
justify-content: space-between;
}
#remove-file {
color: #ef4444;
cursor: pointer;
font-weight: bold;
padding: 0 5px;
}
.typing-indicator {
display: flex;
gap: 4px;
padding: 8px;
align-self: flex-end;
}
.typing-indicator span {
width: 8px;
height: 8px;
background-color: #9ca3af;
border-radius: 50%;
animation: bounce 1.3s infinite both;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.15s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.3s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
</style>
</head>
<body>
<header>دستیار هوشمند Gemma 4 (12B)</header>
<div id="chat-container">
<div class="message bot-message">سلام! من دستیار هوش مصنوعی شما هستم. می‌توانید متن، تصویر یا فایل صوتی برای من ارسال کنید تا آن را تحلیل کنم.</div>
</div>
<div id="input-container">
<div id="attachment-bar">
<span id="file-name">فایلی انتخاب نشده</span>
<span id="remove-file" onclick="clearAttachment()"></span>
</div>
<div class="input-row">
<label for="file-input" class="file-label">
📎
</label>
<input type="file" id="file-input" accept="image/*,audio/*,video/*" onchange="handleFileSelect(this)">
<textarea id="user-input" placeholder="پیام خود را بنویسید..." rows="1" oninput="autoGrow(this)"></textarea>
<button id="send-btn" class="btn" onclick="sendMessage()"></button>
</div>
</div>
<script>
const SPACE_API_URL = "https://huggingface-projects-gemma-4-12b-it.hf.space";
let selectedFileData = null;
function autoGrow(element) {
element.style.height = "48px";
element.style.height = (element.scrollHeight) + "px";
}
function generateSessionHash() {
return Math.random().toString(36).substring(2, 13);
}
function handleFileSelect(input) {
if (input.files && input.files[0]) {
const file = input.files[0];
document.getElementById('file-name').innerText = file.name;
document.getElementById('attachment-bar').style.display = 'flex';
selectedFileData = file;
}
}
function clearAttachment() {
document.getElementById('file-input').value = "";
document.getElementById('attachment-bar').style.display = 'none';
selectedFileData = null;
}
async function uploadFile(file) {
const formData = new FormData();
formData.append('files', file);
const response = await fetch(`${SPACE_API_URL}/gradio_api/upload`, {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('خطا در آپلود فایل به سرور');
const result = await response.json();
return result[0]; // بازگرداندن مسیر فایل روی سرور
}
async function sendMessage() {
const inputEl = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const prompt = inputEl.value.trim();
if (!prompt && !selectedFileData) return;
// غیرفعال کردن موقت ورودی‌ها
inputEl.value = '';
inputEl.style.height = "48px";
inputEl.disabled = true;
sendBtn.disabled = true;
// نمایش پیام کاربر در صفحه چت
const chatContainer = document.getElementById('chat-container');
const userMsgDiv = document.createElement('div');
userMsgDiv.className = 'message user-message';
userMsgDiv.innerText = prompt;
let currentFile = selectedFileData;
let fileUrlForDisplay = '';
if (currentFile) {
fileUrlForDisplay = URL.createObjectURL(currentFile);
if (currentFile.type.startsWith('image/')) {
const img = document.createElement('img');
img.src = fileUrlForDisplay;
img.className = 'media-preview';
userMsgDiv.appendChild(img);
} else if (currentFile.type.startsWith('audio/')) {
const aud = document.createElement('audio');
aud.src = fileUrlForDisplay;
aud.controls = true;
aud.className = 'media-preview';
userMsgDiv.appendChild(aud);
}
}
chatContainer.appendChild(userMsgDiv);
clearAttachment();
chatContainer.scrollTop = chatContainer.scrollHeight;
// نمایش پویانمایی در حال تایپ ربات
const typingDiv = document.createElement('div');
typingDiv.className = 'typing-indicator bot-message';
typingDiv.innerHTML = '<span></span><span></span><span></span>';
chatContainer.appendChild(typingDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
try {
let uploadedPath = null;
if (currentFile) {
uploadedPath = await uploadFile(currentFile);
}
const sessionHash = generateSessionHash();
// آماده‌سازی آرایه فایل بر اساس ساختار لاگ ارسالی شما
const filesParam = uploadedPath ? [uploadedPath] : [];
// ایجاد درخواست به صف Gradio
const queueResponse = await fetch(`${SPACE_API_URL}/gradio_api/queue/join?`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
data: [
prompt,
filesParam,
[],
false, // thinking
4000, // max_new_tokens
560, // max_soft_tokens
"شما یک دستیار هوش مصنوعی بسیار باهوش، مفید و مودب به زبان فارسی هستید.",
1, 0.95, 64, 1
],
fn_index: 0,
session_hash: sessionHash
})
});
if (!queueResponse.ok) throw new Error('سرور شلوغ است یا پاسخ نمی‌دهد.');
const queueData = await queueResponse.json();
const eventId = queueData.event_id;
// پولینگ (Polling) برای دریافت پاسخ نهایی از صف سرور
let isFinished = false;
let botAnswer = "خطا در دریافت پاسخ نهایی";
while (!isFinished) {
const statusResponse = await fetch(`${SPACE_API_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
if (statusResponse.ok) {
const textData = await statusResponse.text();
// پردازش خط به خط داده‌های استریم SSE دیتا متداول در Gradio
const lines = textData.split('\n');
for (let line of lines) {
if (line.startsWith('data:')) {
const jsonData = JSON.parse(line.substring(5));
if (jsonData.msg === 'process_completed') {
const output = jsonData.output.data[0];
botAnswer = typeof output === 'object' ? (output.text || JSON.stringify(output)) : output;
isFinished = true;
break;
} else if (jsonData.msg === 'process_generating' && jsonData.output && jsonData.output.data) {
// پشتیبانی سطحی از استریم آنی متن در صورت ارسال توسط سرور
const output = jsonData.output.data[0];
botAnswer = typeof output === 'object' ? (output.text || botAnswer) : output;
}
}
}
}
if (!isFinished) {
await new Promise(resolve => setTimeout(resolve, 1000)); // ۱ ثانیه صبر بین هر آپدیت وضعیت
}
}
// حذف انیمیشن وضعیت در حال تایپ
chatContainer.removeChild(typingDiv);
// نمایش نهایی پاسخ ربات
const botMsgDiv = document.createElement('div');
botMsgDiv.className = 'message bot-message';
botMsgDiv.innerText = botAnswer;
chatContainer.appendChild(botMsgDiv);
} catch (error) {
console.error(error);
chatContainer.removeChild(typingDiv);
const errorMsgDiv = document.createElement('div');
errorMsgDiv.className = 'message bot-message';
errorMsgDiv.style.color = '#ef4444';
errorMsgDiv.innerText = `⚠️ خطا: ${error.message}. لطفا مجددا تلاش کنید یا از حجم فایل خود بکاهید.`;
chatContainer.appendChild(errorMsgDiv);
} finally {
// فعال‌سازی مجدد فیلدها جهت ارسال پیام بعدی
inputEl.disabled = false;
sendBtn.disabled = false;
inputEl.focus();
chatContainer.scrollTop = chatContainer.scrollHeight;
}
}
// ارسال پیام با زدن دکمه اینتر (بدون Shift)
document.getElementById('user-input').addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
</script>
</body>
</html>