| | <!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}} |
| | |
| | |
| | .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> |