🐘
X2.php
Back
📝 Php ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
<?php // index.php session_start(); // === CONFIG === $DEFAULT_MODEL = 'deepseek-chat'; $DEFAULT_TEMPERATURE = 0.7; $DEFAULT_MAXTOKENS = 800; $MAX_HISTORY_MESSAGES = 12; // Session buckets if (!isset($_SESSION['chat_log'])) $_SESSION['chat_log'] = []; if (!isset($_SESSION['usage_totals'])) $_SESSION['usage_totals'] = ['prompt'=>0,'completion'=>0,'total'=>0]; if (!isset($_SESSION['last_usage'])) $_SESSION['last_usage'] = null; // Handle UI-specific actions if (isset($_POST['action']) && $_POST['action'] === 'clear_chat') { $_SESSION['chat_log'] = []; $_SESSION['usage_totals'] = ['prompt'=>0,'completion'=>0,'total'=>0]; $_SESSION['last_usage'] = null; } // Helpers function h($s){ return htmlspecialchars($s ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } // Render markdown with fenced code blocks (for nicer formatting) function render_markdown_with_code_separators($text) { $text = str_replace(["\r\n", "\r"], "\n", $text); $pat = '/```([a-zA-Z0-9_\-]+)?\s*\n([\s\S]*?)```/m'; $html=''; $pos=0; while (preg_match($pat,$text,$m,PREG_OFFSET_CAPTURE,$pos)) { $start=$m[0][1]; $len=strlen($m[0][0]); $lang=$m[1][0]??''; $code=$m[2][0]??''; $before = substr($text,$pos,$start-$pos); if ($before!=='') $html.='<div class="md-p">'.nl2br(h($before)).'</div>'; $html.='<div class="code-sep"></div>'; $html.='<div class="codeblock">'.($lang?'<div class="code-lang">'.h($lang).'</div>':'') .'<pre><code>'.h($code).'</code></pre></div>'; $pos = $start+$len; } if ($pos < strlen($text)) { $tail = substr($text,$pos); if ($tail!=='') $html.='<div class="md-p">'.nl2br(h($tail)).'</div>'; } if ($html==='') $html = '<div class="md-p">'.nl2br(h($text)).'</div>'; return $html; } // Sticky UI values $stickyModel = $_POST['model'] ?? $DEFAULT_MODEL; $stickyTemp = $_POST['temperature'] ?? $DEFAULT_TEMPERATURE; $stickyMaxTokens = (int)($_POST['max_tokens'] ?? $DEFAULT_MAXTOKENS); $chatLog = $_SESSION['chat_log']; $lastUsage = $_SESSION['last_usage']; $usageTotals = $_SESSION['usage_totals']; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>DeepSeek — General Q&A</title> <style> :root{ --bg:#0f1115; --panel:#151823; --ink:#e8e8e8; --muted:#9aa0a6; --br:#252a36; --accent:#2f6feb; --bot:#2a2f3a; --sep:#2b3242; --code:#0b0e14; --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --gradient-secondary: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); --glow-color: #4f46e5; } *{box-sizing:border-box} html,body{height:100%;margin:0} body{background:var(--bg);color:var(--ink);font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;display:flex;flex-direction:column} .header{height:56px;background:var(--panel);border-bottom:1px solid var(--br);display:flex;align-items:center;justify-content:space-between;padding:0 12px} .title{font-weight:600} .actions{display:flex;gap:8px} .btn{background:#1b1f2a;color:var(--ink);border:1px solid var(--br);border-radius:10px;padding:8px 12px;cursor:pointer;font-size:.95rem} .btn:hover{border-color:var(--accent)} .main{flex:1;display:flex;justify-content:center} .chat-wrap{width:100%;max-width:960px;padding:16px;overflow:auto} .chat{display:flex;flex-direction:column;gap:12px;padding-bottom:160px} .msg{display:flex;gap:10px;align-items:flex-start} .msg .bubble{max-width:min(780px,92%);padding:12px 14px;border-radius:14px;border:1px solid var(--br);background:var(--bot)} .msg.user{justify-content:flex-end} .msg.user .bubble{background:#0e2240;border-color:#17345a} .badge{font-size:.75rem;color:var(--muted);margin-bottom:4px} .md-p{margin:.4rem 0;line-height:1.45} .code-sep{height:10px;border-top:3px double var(--sep);margin:.8rem 0} .codeblock{border:1px solid var(--sep);border-radius:10px;background:var(--code)} .code-lang{font-size:.8rem;color:#cbd5e1;padding:.4rem .6rem;border-bottom:1px solid var(--sep);background:#0f1521;border-top-left-radius:10px;border-top-right-radius:10px} pre{margin:0;padding:.75rem;overflow:auto;white-space:pre;color:#e6edf3} .composer{position:fixed;left:0;right:0;bottom:0;background:var(--panel);border-top:1px solid var(--br)} .composer-inner{max-width:960px;margin:0 auto;display:grid;grid-template-columns:1fr auto;gap:8px;padding:12px;position:relative} .input{border:1px solid var(--br);background:#0d111a;color:var(--ink);border-radius:12px;padding:12px 14px;font-size:1rem;width:100%} .send{min-width:88px;border:1px solid var(--br);background:var(--accent);color:#fff;border-radius:12px;font-weight:600;cursor:pointer} .send:hover{filter:brightness(1.05)} .tokens{grid-column:1 / -1; display:flex; justify-content:space-between; align-items:center; font-size:.85rem; color:var(--muted); padding:4px 2px} .loading-indicator{display:none;align-items:center;gap:8px;padding:12px 14px;color:var(--muted);font-style:italic;grid-column:1;justify-self:start} .loading-indicator::before{content:'';width:18px;height:18px;border:2px solid var(--muted);border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite} @keyframes spin{to{transform:rotate(360deg)}} .overlay{position:fixed;inset:0;display:none;align-items:center;justify-content:center;z-index:1000;background:rgba(0,0,0,.5);backdrop-filter:blur(5px);} .dialog{width:min(92vw,860px);max-height:90vh;overflow:hidden;background:#0c1018;border:1px solid var(--br);border-radius:16px;box-shadow:0 10px 40px rgba(0,0,0,.5);display:flex;flex-direction:column} .dialog-bd{padding:16px;overflow-y:auto;} .dialog-hd{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border-bottom:1px solid var(--br)} .dialog-ft{padding:12px 16px;border-top:1px solid var(--br);display:flex;gap:8px;justify-content:flex-end} .close-x{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer} .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px} label{font-size:.9rem;color:#cbd5e1;display:flex;flex-direction:column;gap:6px} select,input,textarea{background:#0d111a;color:#e5e7eb;border:1px solid var(--br);border-radius:10px;padding:10px 12px;font-size:.95rem;font-family:inherit} .small-note{color:var(--muted);font-size:.85rem} textarea.json-input { min-height: 200px; font-family: 'Courier New', monospace; font-size: 0.85rem; line-height: 1.4; } .mode-toggle { display: flex; align-items: center; gap: 10px; } /* Enhanced CSS for flashcard loading screen */ #flashcard-display { display: none; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 20px; min-height: 140px; background: linear-gradient(135deg, rgba(79, 70, 229, 0.1) 0%, rgba(99, 102, 241, 0.05) 100%); border-radius: 16px; padding: 24px; position: relative; overflow: hidden; } #flashcard-display::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.03), transparent); animation: shimmer 2s infinite; } @keyframes shimmer { 0% { left: -100%; } 100% { left: 100%; } } .spinner-container { position: relative; display: flex; align-items: center; justify-content: center; width: 80px; height: 80px; } .cool-spinner { width: 60px; height: 60px; position: relative; display: none; } .spinner-ring { position: absolute; width: 100%; height: 100%; border-radius: 50%; border: 3px solid transparent; } .spinner-ring:nth-child(1) { border-top: 3px solid var(--glow-color); animation: spin-outer 1.5s linear infinite; filter: drop-shadow(0 0 8px var(--glow-color)); } .spinner-ring:nth-child(2) { border-bottom: 3px solid #667eea; animation: spin-inner 1s linear infinite reverse; filter: drop-shadow(0 0 6px #667eea); } .spinner-ring:nth-child(3) { border-left: 3px solid #764ba2; animation: spin-middle 2s linear infinite; filter: drop-shadow(0 0 4px #764ba2); } @keyframes spin-outer { 0% { transform: rotate(0deg) scale(1); } 50% { transform: rotate(180deg) scale(1.1); } 100% { transform: rotate(360deg) scale(1); } } @keyframes spin-inner { 0% { transform: rotate(0deg) scale(0.8); } 50% { transform: rotate(-180deg) scale(0.9); } 100% { transform: rotate(-360deg) scale(0.8); } } @keyframes spin-middle { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .spinner-pulse { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 12px; height: 12px; background: var(--glow-color); border-radius: 50%; animation: pulse-glow 1.5s ease-in-out infinite; } @keyframes pulse-glow { 0%, 100% { transform: translate(-50%, -50%) scale(1); box-shadow: 0 0 0 0 rgba(79, 70, 229, 0.7); } 50% { transform: translate(-50%, -50%) scale(1.2); box-shadow: 0 0 0 20px rgba(79, 70, 229, 0); } } #flashcard-question, #flashcard-answer { font-size: 1.1rem; font-weight: 600; color: var(--accent); opacity: 0; transform: translateY(20px); transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); } #flashcard-question.show, #flashcard-answer.show { opacity: 1; transform: translateY(0); } #flashcard-answer { font-size: 1rem; color: var(--ink); font-weight: normal; font-style: italic; background: rgba(255, 255, 255, 0.05); padding: 12px 20px; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.1); } .loading-status { font-size: 0.9rem; color: var(--muted); font-weight: 500; letter-spacing: 0.5px; opacity: 0.8; margin-top: 8px; } .dots { display: inline-block; animation: loading-dots 1.5s infinite; } @keyframes loading-dots { 0%, 20% { content: ''; } 40% { content: '.'; } 60% { content: '..'; } 80%, 100% { content: '...'; } } </style> </head> <body> <div class="header"> <div class="title">DeepSeek — General Q&A</div> <div class="actions"> <form method="post" style="margin:0"> <button class="btn" name="action" value="clear_chat" onclick="return confirm('Clear chat transcript and counters?')">Clear Chat</button> </form> <button class="btn" onclick="showSettings()">Settings</button> </div> </div> <div class="main"> <div class="chat-wrap"> <div class="chat" id="chat"> <?php if (empty($chatLog)): ?> <div class="msg" style="justify-content:center;opacity:.8"> <div class="bubble">Ask anything—history, science, how-to, travel tips, you name it.</div> </div> <?php else: foreach ($chatLog as $m): $isUser = $m['role']==='user'; ?> <div class="msg <?= $isUser?'user':'' ?>"> <div class="bubble"> <div class="badge"><?= $isUser?'You':'Assistant' ?></div> <div class="content"> <?php if ($isUser): ?> <div class="md-p"><?= nl2br(h($m['content'])) ?></div> <?php else: ?> <?= render_markdown_with_code_separators($m['content']) ?> <?php endif; ?> </div> </div> </div> <?php endforeach; endif; ?> </div> </div> </div> <div class="composer"> <div class="composer-inner"> <div id="input-container" style="display:grid;grid-template-columns:1fr auto;gap:8px"> <input type="text" id="questionInput" class="input" placeholder="Type your question..." autofocus> <button class="send" id="sendButton">Send</button> </div> <div id="flashcard-display"> <div class="spinner-container"> <div class="cool-spinner" id="flashcard-spinner"> <div class="spinner-ring"></div> <div class="spinner-ring"></div> <div class="spinner-ring"></div> <div class="spinner-pulse"></div> </div> </div> <div class="loading-status"> Thinking<span class="dots"></span> </div> <div id="flashcard-question"></div> <div id="flashcard-answer"></div> </div> <div class="tokens"> <div id="last-usage"> <?php if ($lastUsage): ?> <strong>Last</strong> — prompt: <?= (int)$lastUsage['prompt'] ?>, completion: <?= (int)$lastUsage['completion'] ?>, total: <?= (int)$lastUsage['total'] ?> <?php else: ?> <strong>Last</strong> — no usage yet <?php endif; ?> </div> <div id="session-totals"> <strong>Session</strong> — prompt: <?= (int)$usageTotals['prompt'] ?>, completion: <?= (int)$usageTotals['completion'] ?>, total: <?= (int)$usageTotals['total'] ?> </div> </div> </div> </div> <div class="overlay" id="settings"> <div class="dialog"> <div class="dialog-hd"> <strong>Settings</strong> <button class="close-x" onclick="hideSettings()">&times;</button> </div> <div class="dialog-bd"> <div class="grid"> <label>Model <select id="modelSelect"> <option value="deepseek-chat" <?= $stickyModel==='deepseek-chat'?'selected':''; ?>>deepseek-chat</option> <option value="deepseek-reasoner" <?= $stickyModel==='deepseek-reasoner'?'selected':''; ?>>deepseek-reasoner (deliberation)</option> <option value="gpt-5-nano" <?= $stickyModel==='gpt-5-nano'?'selected':''; ?>>gpt-5-nano (OpenAI)</option> <option value="gpt-5-mini" <?= $stickyModel==='gpt-5-mini'?'selected':''; ?>>gpt-5-mini (OpenAI)</option> <option value="gpt-5" <?= $stickyModel==='gpt-5'?'selected':''; ?>>gpt-5 (OpenAI)</option> <option value="gpt-4o" <?= $stickyModel==='gpt-4o'?'selected':''; ?>>gpt-4o (OpenAI)</option> <option value="gpt-4o-mini" <?= $stickyModel==='gpt-4o-mini'?'selected':''; ?>>gpt-4o-mini (OpenAI)</option> <!-- Added xAI Grok models --> <option value="grok-3" <?= $stickyModel==='grok-3'?'selected':''; ?>>grok-3 (xAI - flagship reasoning)</option> <option value="grok-3-mini" <?= $stickyModel==='grok-3-mini'?'selected':''; ?>>grok-3-mini (xAI - lightweight reasoning)</option> <option value="grok-code-fast-1" <?= $stickyModel==='grok-code-fast-1'?'selected':''; ?>>grok-code-fast-1 (xAI - speedy coding)</option> </select> </label> <label>Max tokens <select id="maxTokensSelect"> <?php foreach ([200,500,800,1000,1500,2000,4000,6000,8000] as $c): ?> <option value="<?= (int)$c ?>" <?= $stickyMaxTokens==$c?'selected':''; ?>><?= (int)$c ?></option> <?php endforeach; ?> </select> </label> <label>Temperature <input id="temperatureInput" type="number" step="0.1" min="0" max="2" value="<?= h($stickyTemp) ?>"> </label> </div> <label style="margin-top: 20px;">Flashcard Q&A JSON URL <input id="flashcardUrlInput" type="text" value="flashcards.json" placeholder="Enter URL to a JSON file"> </label> <button class="btn" style="width:100%; margin-top: 10px;" onclick="loadFlashcards()">Load Flashcards</button> <div class="small-note"> Chat memory is <strong>ON</strong> (last <?= (int)$MAX_HISTORY_MESSAGES ?> messages are sent with your next question). </div> </div> <div class="dialog-ft"> <button class="btn" onclick="hideSettings()">Close</button> </div> </div> </div> <script> // Settings overlay function showSettings(){ document.getElementById('settings').style.display='flex'; } function hideSettings(){ document.getElementById('settings').style.display='none'; } document.getElementById('settings').addEventListener('click', function(e){ if(e.target===this) hideSettings(); }); document.addEventListener('keydown', function(e){ if(e.key==='Escape') hideSettings(); }); // Auto-scroll chat to bottom function autoScroll(){ const wrap = document.querySelector('.chat-wrap'); if (wrap) wrap.scrollTop = wrap.scrollHeight; } window.addEventListener('load', autoScroll); // Main chat logic using AJAX document.getElementById('sendButton').addEventListener('click', sendMessage); document.getElementById('questionInput').addEventListener('keydown', function(e){ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); // Prevent form submission sendMessage(); } }); const questionInput = document.getElementById('questionInput'); const sendButton = document.getElementById('sendButton'); const chatDiv = document.getElementById('chat'); const inputContainer = document.getElementById('input-container'); const flashcardDisplay = document.getElementById('flashcard-display'); const flashcardQuestion = document.getElementById('flashcard-question'); const flashcardAnswer = document.getElementById('flashcard-answer'); const flashcardSpinner = document.getElementById('flashcard-spinner'); const flashcardUrlInput = document.getElementById('flashcardUrlInput'); // This function is for client-side markdown rendering (simplified) function renderMarkdown(text) { let html = text; // Basic replacements for bold, italic, code blocks html = html.replace(/```([a-zA-Z0-9_\-]+)?\s*\n([\s\S]*?)```/g, (match, lang, code) => { return ` <div class="code-sep"></div> <div class="codeblock"> ${lang ? `<div class="code-lang">${lang}</div>` : ''} <pre><code>${code.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</code></pre> </div> `; }); // Replace remaining newlines with <br> for paragraphs html = html.replace(/\n/g, '<br>'); return `<div class="md-p">${html}</div>`; } let flashcardTimer = null; let flashcardsData = []; // Function to fetch and load flashcard data async function loadFlashcards() { const url = flashcardUrlInput.value; try { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch flashcards. Status: ${response.status}`); } flashcardsData = await response.json(); alert('Flashcards loaded successfully!'); } catch (e) { console.error("Error loading flashcards:", e); alert(`Error loading flashcards: ${e.message}. Please check the URL.`); flashcardsData = []; // Clear data on error } } // Automatically load the default flashcards on page load window.addEventListener('load', () => { loadFlashcards(); autoScroll(); }); // Function to start the flashcard animation with cool spinner function startFlashcardAnimation() { // Hide the input container and show the flashcard container inputContainer.style.display = 'none'; flashcardDisplay.style.display = 'flex'; // Show the cool spinner flashcardSpinner.style.display = 'block'; // Hide any existing flashcard content flashcardQuestion.classList.remove('show'); flashcardAnswer.classList.remove('show'); flashcardQuestion.textContent = ''; flashcardAnswer.textContent = ''; // If we have flashcards data, cycle through them if (flashcardsData.length > 0) { function showNextFlashcard() { const randomIndex = Math.floor(Math.random() * flashcardsData.length); const flashcard = flashcardsData[randomIndex]; // Reset content and show spinner flashcardQuestion.classList.remove('show'); flashcardAnswer.classList.remove('show'); flashcardSpinner.style.display = 'block'; // Display question after spinner shows for a bit setTimeout(() => { flashcardQuestion.textContent = flashcard.question; flashcardQuestion.classList.add('show'); }, 1200); // Show answer after question displays setTimeout(() => { flashcardQuestion.classList.remove('show'); flashcardAnswer.textContent = flashcard.answer; flashcardAnswer.classList.add('show'); }, 3500); // Hide answer before next cycle setTimeout(() => { flashcardAnswer.classList.remove('show'); }, 4800); } // Start the cycle and repeat every 5 seconds showNextFlashcard(); flashcardTimer = setInterval(showNextFlashcard, 5000); } } // Function to stop the flashcard animation function stopFlashcardAnimation() { clearInterval(flashcardTimer); flashcardDisplay.style.display = 'none'; inputContainer.style.display = 'grid'; // Hide spinner and clear content flashcardSpinner.style.display = 'none'; flashcardQuestion.classList.remove('show'); flashcardAnswer.classList.remove('show'); flashcardQuestion.textContent = ''; flashcardAnswer.textContent = ''; } async function sendMessage() { const question = questionInput.value.trim(); if (question === '') return; // Show user's message immediately const userMessageHTML = `<div class="msg user"><div class="bubble"><div class="badge">You</div><div class="content"><div class="md-p">${question.replace(/\n/g, '<br>')}</div></div></div></div>`; chatDiv.innerHTML += userMessageHTML; autoScroll(); // Start the loading animation startFlashcardAnimation(); // Disable UI questionInput.disabled = true; sendButton.disabled = true; questionInput.value = ''; const model = document.getElementById('modelSelect').value; const maxTokens = document.getElementById('maxTokensSelect').value; const temperature = document.getElementById('temperatureInput').value; try { const response = await fetch('api.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question, model, maxTokens, temperature }) }); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const data = await response.json(); if (data.error) { throw new Error(data.error); } // Append assistant's message const assistantMessageHTML = ` <div class="msg"> <div class="bubble"> <div class="badge">Assistant</div> <div class="content"> ${renderMarkdown(data.answer)} </div> </div> </div>`; chatDiv.innerHTML += assistantMessageHTML; // Update token counters if (data.usage) { document.getElementById('last-usage').innerHTML = `<strong>Last</strong> — prompt: ${data.usage.prompt_tokens}, completion: ${data.usage.completion_tokens}, total: ${data.usage.total_tokens}`; document.getElementById('session-totals').innerHTML = `<strong>Session</strong> — prompt: ${data.usage.total_prompt}, completion: ${data.usage.total_completion}, total: ${data.usage.total_tokens}`; } } catch (error) { console.error('Fetch error:', error); const errorMessageHTML = `<div class="msg"><div class="bubble" style="background:#3b1f28;border-color:#5a2836;color:#ffccd5;font-style:normal"><strong>Error:</strong> ${error.message}</div></div>`; chatDiv.innerHTML += errorMessageHTML; } finally { // Stop the loading animation and re-enable input stopFlashcardAnimation(); questionInput.disabled = false; sendButton.disabled = false; questionInput.focus(); autoScroll(); } } </script> </body> </html>