🐘
chat.php
Back
📝 Php ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
<?php // chat.php - Chat Window & Functionality // Render markdown helper 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; } ?> <style> /* Chat-specific styles */ .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} /* Flashcard animations */ #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%; } } .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> <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> <script> // Chat functionality document.getElementById('sendButton').addEventListener('click', sendMessage); document.getElementById('questionInput').addEventListener('keydown', function(e){ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); 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'); // Markdown rendering function renderMarkdown(text) { let html = text; 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> `; }); html = html.replace(/\n/g, '<br>'); return `<div class="md-p">${html}</div>`; } // Flashcard system let flashcardTimer = null; let flashcardsData = []; async function loadFlashcards() { try { const response = await fetch('flashcards.json'); if (!response.ok) throw new Error(`Failed to fetch flashcards. Status: ${response.status}`); flashcardsData = await response.json(); } catch (e) { console.error("Error loading flashcards:", e); flashcardsData = []; } } function startFlashcardAnimation() { inputContainer.style.display = 'none'; flashcardDisplay.style.display = 'flex'; flashcardSpinner.style.display = 'block'; flashcardQuestion.classList.remove('show'); flashcardAnswer.classList.remove('show'); flashcardQuestion.textContent = ''; flashcardAnswer.textContent = ''; if (flashcardsData.length > 0) { function showNextFlashcard() { const randomIndex = Math.floor(Math.random() * flashcardsData.length); const flashcard = flashcardsData[randomIndex]; flashcardQuestion.classList.remove('show'); flashcardAnswer.classList.remove('show'); flashcardSpinner.style.display = 'block'; setTimeout(() => { flashcardQuestion.textContent = flashcard.question; flashcardQuestion.classList.add('show'); }, 1200); setTimeout(() => { flashcardQuestion.classList.remove('show'); flashcardAnswer.textContent = flashcard.answer; flashcardAnswer.classList.add('show'); }, 3500); setTimeout(() => { flashcardAnswer.classList.remove('show'); }, 4800); } showNextFlashcard(); flashcardTimer = setInterval(showNextFlashcard, 5000); } } function stopFlashcardAnimation() { clearInterval(flashcardTimer); flashcardDisplay.style.display = 'none'; inputContainer.style.display = 'grid'; 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 = ''; try { // Get system prompt based on current setting let systemPrompt = ''; if (currentInitialPrompt !== 'none') { const promptOptions = { 'helpful': 'You are a helpful, knowledgeable, and friendly AI assistant. Provide clear, accurate, and detailed responses. Always be respectful and aim to be as useful as possible.', 'creative': 'You are a creative and imaginative AI assistant. Help with brainstorming, creative writing, artistic ideas, and innovative solutions. Be inspiring and think outside the box while remaining practical.', 'technical': 'You are a technical expert and programming assistant. Provide precise, well-structured code examples, explain technical concepts clearly, and offer best practices. Focus on accuracy and efficiency.' }; systemPrompt = promptOptions[currentInitialPrompt] || ''; } const response = await fetch('api.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question, model: 'deepseek-chat', // You can make this dynamic later maxTokens: 800, temperature: 0.7, system: systemPrompt }) }); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const data = await response.json(); if (data.error) { throw new Error(data.error); } if (data.warning) { console.warn('API Warning:', data.warning); } // 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_cumulative}`; } } 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 { stopFlashcardAnimation(); questionInput.disabled = false; sendButton.disabled = false; questionInput.focus(); autoScroll(); } } // Load flashcards on page load window.addEventListener('load', loadFlashcards); </script>