HtmlEzmar / index.html
Hamed744's picture
Update index.html
a29de52 verified
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>چت‌بات Gemini 2.5 (Flash / Pro) - استریم + تفکر</title>
<style>
:root{
--bg:#0f1221; --muted:#aab0d6;
--primary:#4f77ff; --accent:#9b5df5;
--bubble-user:#4f77ff; --bubble-bot:#1a1f3f;
--border:rgba(255,255,255,.08);
--shadow:0 10px 30px rgba(0,0,0,.25), 0 0 0 1px rgba(255,255,255,.03) inset;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0; font-family: Vazirmatn, Tahoma, Arial, sans-serif;
background: radial-gradient(1200px 600px at 80% -100px, rgba(79,119,255,.35), transparent 60%),
radial-gradient(900px 500px at -20% -60px, rgba(155,93,245,.35), transparent 55%),
var(--bg);
color:#e7e9ff; display:flex; align-items:center; justify-content:center;
}
.chat{
width:min(980px, 96vw); height:92vh; overflow:hidden;
background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03));
border:1px solid var(--border); border-radius:20px; box-shadow:var(--shadow);
display:flex; flex-direction:column; backdrop-filter: blur(10px);
}
.header{
padding:14px 18px; gap:12px;
background:linear-gradient(90deg, rgba(79,119,255,.25), rgba(155,93,245,.25));
border-bottom:1px solid var(--border);
display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap;
}
.brand{display:flex; align-items:center; gap:12px; font-weight:700;}
.logo{width:36px; height:36px; border-radius:50%;
background: conic-gradient(from 210deg, var(--primary), var(--accent), var(--primary));
box-shadow:0 0 0 3px rgba(255,255,255,.08) inset, 0 0 20px rgba(155,93,245,.35);}
.model-picker{display:flex; align-items:center; gap:8px}
select{
appearance:none; -webkit-appearance:none;
padding:8px 12px; border-radius:10px; border:1px solid var(--border);
background:rgba(255,255,255,.08); color:#fff; outline:none; cursor:pointer;
}
.messages{
flex:1; overflow:auto; padding:18px; display:flex; flex-direction:column; gap:14px;
scroll-behavior:smooth;
}
.msg{max-width:75%; padding:12px 14px; border-radius:18px; line-height:1.7; word-break:break-word;
box-shadow:0 6px 16px rgba(0,0,0,.15)}
.bot{align-self:flex-start; background:var(--bubble-bot); border:1px solid var(--border)}
.user{align-self:flex-end; background:var(--bubble-user); color:#fff}
/* پنل تفکر */
.thinking{align-self:flex-start; width:min(680px, 100%); margin:4px 0 2px;}
.thinking-head{
display:flex; align-items:center; justify-content:space-between; gap:10px;
padding:10px 12px; border:1px dashed rgba(255,255,255,.16); border-bottom:none;
border-radius:14px 14px 0 0; background:rgba(255,255,255,.04); cursor:pointer; user-select:none;
}
.thinking-title{display:flex; align-items:center; gap:10px; color:#ffd666; font-weight:600;}
.chev{font-size:12px; opacity:.8; transition:transform .3s}
.chev.collapsed{transform:rotate(-90deg)}
.thinking-body{
border:1px dashed rgba(255,255,255,.16); border-top:none; border-radius:0 0 14px 14px;
background:rgba(255,255,255,.02); padding:12px 14px; color:#e7e9ff; font-size:.92rem; line-height:1.75;
max-height:280px; overflow:auto; white-space:pre-wrap;
}
.thinking-body.collapsed{display:none}
.thinking-body .chunk{
display:block; padding:6px 8px; margin:4px 0;
background:rgba(255,255,255,.03); border:1px solid var(--border); border-radius:10px;
}
/* کرسر پاسخ */
.cursor{display:inline-block; width:2px; height:1em; background:#e7e9ff; animation:blink 1s infinite; vertical-align:text-bottom}
@keyframes blink{0%,50%{opacity:1}51%,100%{opacity:0}}
/* SVG اتمیِ چرخان */
.atom{width:26px; height:26px; flex:none; filter: drop-shadow(0 0 6px rgba(155,93,245,.45));}
.atom .orbit{transform-origin:50% 50%; transform-box:fill-box;}
.atom.spinning .o1{animation:spin 2.8s linear infinite}
.atom.spinning .o2{animation:spin 3.4s linear infinite reverse}
.atom.spinning .o3{animation:spin 2.0s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.input{
padding:14px; border-top:1px solid var(--border);
background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));
display:flex; gap:10px; align-items:center;
}
.input input{
flex:1; padding:14px 16px; border-radius:14px; border:1px solid var(--border);
outline:none; background:rgba(255,255,255,.06); color:#fff;
}
.input button{
padding:14px 18px; border:none; border-radius:12px; font-weight:600; cursor:pointer; color:#fff;
background:linear-gradient(90deg, var(--primary), var(--accent)); box-shadow:0 10px 20px rgba(79,119,255,.3);
transition:transform .06s ease;
}
.input button:disabled{opacity:.6; cursor:not-allowed}
.input button:active{transform:translateY(1px)}
.messages::-webkit-scrollbar, .thinking-body::-webkit-scrollbar{width:10px}
.messages::-webkit-scrollbar-thumb, .thinking-body::-webkit-scrollbar-thumb{
background:linear-gradient(180deg, var(--primary), var(--accent)); border-radius:20px;
}
</style>
</head>
<body>
<div class="chat">
<div class="header">
<div class="brand">
<div class="logo" aria-hidden="true"></div>
<div>
<div>چت‌بات Gemini 2.5</div>
<small>استریم • تفکر داخلی (پویا)</small>
</div>
</div>
<!-- انتخاب‌گر مدل -->
<div class="model-picker">
<label for="modelSelect">مدل:</label>
<select id="modelSelect">
<option value="gemini-2.5-flash" selected>2.5 Flash</option>
<option value="gemini-2.5-pro">2.5 Pro</option>
</select>
</div>
</div>
<div id="messages" class="messages">
<div class="msg bot">سلام! من Gemini 2.5 هستم. پاسخ‌ها را به‌صورت زنده می‌بینی ✨</div>
</div>
<div class="input">
<input id="messageInput" type="text" placeholder="پیام خود را بنویسید…" onkeypress="if(event.key==='Enter') sendMessage();"/>
<button id="sendButton" onclick="sendMessage()">ارسال</button>
</div>
</div>
<script>
// ===== تنظیمات =====
const API_KEY = 'AIzaSyDQDzg763n_krAgr5nHcrlWbn_szfIsVdk';
const API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
// تفکر: ذاتی/پویا؛ فقط اگر thought=true بیاید، نمایش داده می‌شود
const INCLUDE_THOUGHTS = true;
const THINKING_BUDGET = -1;
// ===== حالت داخلی =====
let currentThinkingBody = null;
let currentResponseMsg = null;
let atomEl = null;
const modelSelect = document.getElementById('modelSelect');
const currentModel = () => modelSelect.value;
function addMessage(html, isUser=false){
const wrap = document.getElementById('messages');
const div = document.createElement('div');
div.className = `msg ${isUser?'user':'bot'}`;
div.innerHTML = html.replace(/\n/g,'<br>');
wrap.appendChild(div);
wrap.scrollTop = wrap.scrollHeight;
return div;
}
function atomSVG(spin){
// SVG اتم با سه مدار بیضوی و الکترون‌ها
return `
<svg class="atom ${spin?'spinning':''}" viewBox="0 0 64 64" aria-hidden="true">
<defs>
<linearGradient id="grad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#4f77ff"/>
<stop offset="100%" stop-color="#9b5df5"/>
</linearGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<!-- هسته -->
<circle cx="32" cy="32" r="4.5" fill="url(#grad)" filter="url(#glow)"/>
<!-- مدار ۱ -->
<g class="orbit o1">
<ellipse cx="32" cy="32" rx="20" ry="10" fill="none" stroke="url(#grad)" stroke-width="2" opacity=".85"/>
<circle cx="52" cy="32" r="2.5" fill="url(#grad)" filter="url(#glow)"/>
</g>
<!-- مدار ۲ -->
<g class="orbit o2" transform="rotate(-60 32 32)">
<ellipse cx="32" cy="32" rx="20" ry="10" fill="none" stroke="url(#grad)" stroke-width="2" opacity=".65"/>
<circle cx="52" cy="32" r="2.3" fill="url(#grad)" filter="url(#glow)"/>
</g>
<!-- مدار ۳ -->
<g class="orbit o3" transform="rotate(60 32 32)">
<ellipse cx="32" cy="32" rx="20" ry="10" fill="none" stroke="url(#grad)" stroke-width="2" opacity=".5"/>
<circle cx="52" cy="32" r="2.1" fill="url(#grad)" filter="url(#glow)"/>
</g>
</svg>`;
}
function createThinkingBlock(){
const wrap = document.getElementById('messages');
const container = document.createElement('div');
container.className = 'thinking';
container.innerHTML = `
<div class="thinking-head" onclick="toggleThinking(this)">
<div class="thinking-title">
${atomSVG(true)}
<span>فرآیند تفکر</span>
</div>
<span class="chev"></span>
</div>
<div class="thinking-body"></div>
`;
wrap.appendChild(container);
wrap.scrollTop = wrap.scrollHeight;
currentThinkingBody = container.querySelector('.thinking-body');
atomEl = container.querySelector('.atom');
}
function toggleThinking(headEl){
const body = headEl.nextElementSibling;
const chev = headEl.querySelector('.chev');
body.classList.toggle('collapsed');
chev.classList.toggle('collapsed');
}
function appendThought(text){
// فقط thought واقعیِ مدل
const span = document.createElement('span');
span.className = 'chunk';
span.innerHTML = text.replace(/\n/g,'<br>');
currentThinkingBody.appendChild(span);
const wrap = document.getElementById('messages');
wrap.scrollTop = wrap.scrollHeight;
}
function removeCursors(){
document.querySelectorAll('.cursor').forEach(c=>c.remove());
}
async function sendMessage(){
const input = document.getElementById('messageInput');
const btn = document.getElementById('sendButton');
const msg = input.value.trim();
if(!msg) return;
addMessage(msg, true);
input.value=''; btn.disabled=true;
// پنل تفکر + آغاز چرخش اتم
createThinkingBlock();
currentResponseMsg = null;
try{
const requestBody = {
contents:[{ parts:[{ text: msg }] }],
generationConfig:{
temperature:0.7, topK:40, topP:0.95, maxOutputTokens:8192,
thinkingConfig:{ thinkingBudget: THINKING_BUDGET, includeThoughts: INCLUDE_THOUGHTS }
}
};
const url = `${API_BASE}/${currentModel()}:streamGenerateContent?alt=sse`;
const res = await fetch(url, {
method:'POST',
headers:{ 'Content-Type':'application/json', 'x-goog-api-key': API_KEY },
body: JSON.stringify(requestBody)
});
if(!res.ok) throw new Error(`HTTP ${res.status}`);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while(true){
const {done, value} = await reader.read();
if(done) break;
buffer += decoder.decode(value, {stream:true});
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for(const line of lines){
if(!line.startsWith('data: ')) continue;
const jsonStr = line.slice(6);
if(jsonStr === '[DONE]') continue;
try{
const data = JSON.parse(jsonStr);
const content = data?.candidates?.[0]?.content;
if(!content?.parts?.length) continue;
for(const part of content.parts){
if(!part?.text) continue;
if(part.thought === true){
appendThought(part.text); // فکر واقعی
}else{
if(!currentResponseMsg){
currentResponseMsg = addMessage('<span class="cursor"></span>', false);
}
const cur = currentResponseMsg.querySelector('.cursor');
if(cur) cur.remove();
currentResponseMsg.innerHTML += part.text.replace(/\n/g,'<br>') + '<span class="cursor"></span>';
}
}
}catch(e){ console.error('parse error', e); }
}
}
}catch(err){
addMessage(`<span style="color:#ffb3b3">خطا: ${err.message}</span>, مدل: ${currentModel()}`, false);
}finally{
if(atomEl) atomEl.classList.remove('spinning'); // توقف چرخش اتم
removeCursors(); btn.disabled=false; input.focus();
}
}
</script>
</body>
</html>