| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>GroqChat</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> |
| <style> |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } |
| |
| :root { |
| --bg: #0d0d0f; |
| --surface: #18181b; |
| --surface-2: #1f1f23; |
| --border: #2a2a30; |
| --accent: #7c6af7; |
| --accent-glow: rgba(124,106,247,0.18); |
| --accent-2: #a78bfa; |
| --text: #e8e8ed; |
| --text-muted: #8b8b99; |
| --text-dim: #55555f; |
| --user-bubble: #1e1b38; |
| --user-border: #3d3580; |
| --success: #4ade80; |
| --radius: 14px; |
| --sidebar: 260px; |
| } |
| |
| html, body { height: 100%; overflow: hidden; } |
| |
| body { |
| font-family: 'Inter', sans-serif; |
| background: var(--bg); |
| color: var(--text); |
| display: flex; |
| font-size: 14px; |
| line-height: 1.6; |
| } |
| |
| |
| .sidebar { |
| width: var(--sidebar); |
| background: var(--surface); |
| border-right: 1px solid var(--border); |
| display: flex; |
| flex-direction: column; |
| flex-shrink: 0; |
| overflow: hidden; |
| } |
| |
| .sidebar-header { |
| padding: 20px 16px 12px; |
| border-bottom: 1px solid var(--border); |
| } |
| |
| .logo { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| margin-bottom: 16px; |
| } |
| |
| .logo-icon { |
| width: 32px; height: 32px; |
| background: linear-gradient(135deg, var(--accent), var(--accent-2)); |
| border-radius: 8px; |
| display: grid; |
| place-items: center; |
| font-size: 16px; |
| flex-shrink: 0; |
| } |
| |
| .logo-text { |
| font-size: 16px; |
| font-weight: 600; |
| letter-spacing: -0.3px; |
| color: var(--text); |
| } |
| .logo-text span { color: var(--accent-2); } |
| |
| .new-chat-btn { |
| width: 100%; |
| padding: 9px 12px; |
| background: var(--accent-glow); |
| border: 1px solid var(--accent); |
| border-radius: 8px; |
| color: var(--accent-2); |
| font-size: 13px; |
| font-weight: 500; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| transition: background 0.15s, opacity 0.15s; |
| font-family: inherit; |
| } |
| .new-chat-btn:hover { background: rgba(124,106,247,0.28); } |
| |
| |
| .model-section { padding: 12px 16px; border-bottom: 1px solid var(--border); } |
| .model-label { font-size: 11px; font-weight: 500; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.8px; margin-bottom: 8px; } |
| |
| .model-option { |
| padding: 9px 11px; |
| border-radius: 8px; |
| cursor: pointer; |
| border: 1px solid transparent; |
| margin-bottom: 4px; |
| transition: all 0.15s; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| .model-option:hover { background: var(--surface-2); } |
| .model-option.active { |
| background: var(--accent-glow); |
| border-color: var(--accent); |
| } |
| .model-icon { font-size: 16px; flex-shrink: 0; } |
| .model-info { min-width: 0; } |
| .model-name { font-size: 12px; font-weight: 500; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| .model-desc { font-size: 11px; color: var(--text-muted); } |
| |
| |
| .history-section { flex: 1; overflow-y: auto; padding: 12px 8px; } |
| .history-label { font-size: 11px; font-weight: 500; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.8px; padding: 0 8px; margin-bottom: 6px; } |
| |
| .history-item { |
| padding: 8px 10px; |
| border-radius: 8px; |
| cursor: pointer; |
| color: var(--text-muted); |
| font-size: 12px; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| transition: all 0.15s; |
| border: 1px solid transparent; |
| } |
| .history-item:hover { background: var(--surface-2); color: var(--text); } |
| .history-item.active { background: var(--surface-2); border-color: var(--border); color: var(--text); } |
| |
| .history-empty { padding: 0 10px; color: var(--text-dim); font-size: 12px; font-style: italic; } |
| |
| |
| .sidebar-footer { |
| padding: 12px 16px; |
| border-top: 1px solid var(--border); |
| font-size: 11px; |
| color: var(--text-dim); |
| text-align: center; |
| } |
| .sidebar-footer a { color: var(--accent); text-decoration: none; } |
| |
| |
| .main { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| background: var(--bg); |
| } |
| |
| |
| .topbar { |
| padding: 14px 24px; |
| border-bottom: 1px solid var(--border); |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| background: var(--surface); |
| flex-shrink: 0; |
| } |
| .topbar-model { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| font-size: 13px; |
| color: var(--text-muted); |
| } |
| .topbar-model .dot { |
| width: 7px; height: 7px; |
| border-radius: 50%; |
| background: var(--success); |
| animation: pulse 2s infinite; |
| } |
| @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} } |
| .topbar-actions { display: flex; gap: 8px; } |
| .icon-btn { |
| background: none; |
| border: 1px solid var(--border); |
| color: var(--text-muted); |
| border-radius: 7px; |
| padding: 6px 10px; |
| cursor: pointer; |
| font-size: 12px; |
| font-family: inherit; |
| transition: all 0.15s; |
| } |
| .icon-btn:hover { background: var(--surface-2); color: var(--text); } |
| |
| |
| .chat-window { |
| flex: 1; |
| overflow-y: auto; |
| padding: 32px 0; |
| scroll-behavior: smooth; |
| } |
| |
| .chat-inner { max-width: 740px; margin: 0 auto; padding: 0 24px; } |
| |
| |
| .welcome { |
| text-align: center; |
| padding: 60px 20px; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 16px; |
| } |
| .welcome-icon { |
| width: 64px; height: 64px; |
| background: linear-gradient(135deg, var(--accent), var(--accent-2)); |
| border-radius: 18px; |
| display: grid; |
| place-items: center; |
| font-size: 32px; |
| box-shadow: 0 0 40px var(--accent-glow); |
| } |
| .welcome h1 { font-size: 26px; font-weight: 600; letter-spacing: -0.5px; } |
| .welcome h1 span { color: var(--accent-2); } |
| .welcome p { color: var(--text-muted); font-size: 14px; max-width: 380px; line-height: 1.7; } |
| |
| .starter-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 8px; width: 100%; max-width: 500px; } |
| .starter-card { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 10px; |
| padding: 14px; |
| cursor: pointer; |
| text-align: left; |
| transition: all 0.15s; |
| font-family: inherit; |
| color: var(--text); |
| } |
| .starter-card:hover { border-color: var(--accent); background: var(--accent-glow); } |
| .starter-card .starter-icon { font-size: 18px; margin-bottom: 6px; display: block; } |
| .starter-card .starter-text { font-size: 12px; color: var(--text-muted); } |
| |
| |
| .message { margin-bottom: 28px; display: flex; flex-direction: column; } |
| .message.user { align-items: flex-end; } |
| .message.assistant { align-items: flex-start; } |
| |
| .msg-role { |
| font-size: 11px; |
| font-weight: 600; |
| text-transform: uppercase; |
| letter-spacing: 0.7px; |
| margin-bottom: 6px; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| } |
| .message.user .msg-role { color: var(--accent-2); } |
| .message.assistant .msg-role { color: var(--text-muted); } |
| |
| .msg-avatar { |
| width: 20px; height: 20px; |
| border-radius: 5px; |
| display: grid; |
| place-items: center; |
| font-size: 10px; |
| } |
| .message.assistant .msg-avatar { background: linear-gradient(135deg, var(--accent), var(--accent-2)); } |
| .message.user .msg-avatar { background: var(--user-border); } |
| |
| .msg-bubble { |
| max-width: 85%; |
| padding: 14px 18px; |
| border-radius: var(--radius); |
| line-height: 1.7; |
| font-size: 14px; |
| } |
| .message.user .msg-bubble { |
| background: var(--user-bubble); |
| border: 1px solid var(--user-border); |
| border-radius: var(--radius) var(--radius) 4px var(--radius); |
| } |
| .message.assistant .msg-bubble { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: var(--radius) var(--radius) var(--radius) 4px; |
| width: 100%; |
| max-width: 100%; |
| } |
| |
| .msg-bubble code { |
| font-family: 'JetBrains Mono', monospace; |
| background: rgba(124,106,247,0.1); |
| border: 1px solid rgba(124,106,247,0.2); |
| padding: 2px 6px; |
| border-radius: 4px; |
| font-size: 12px; |
| color: var(--accent-2); |
| } |
| .msg-bubble pre { |
| background: #111117; |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| padding: 14px; |
| overflow-x: auto; |
| margin: 10px 0; |
| } |
| .msg-bubble pre code { |
| background: none; |
| border: none; |
| padding: 0; |
| font-size: 13px; |
| color: #c9d1d9; |
| } |
| |
| .msg-bubble p { margin-bottom: 10px; } |
| .msg-bubble p:last-child { margin-bottom: 0; } |
| .msg-bubble ul, .msg-bubble ol { padding-left: 20px; margin: 8px 0; } |
| .msg-bubble li { margin-bottom: 4px; } |
| .msg-bubble strong { color: var(--text); font-weight: 600; } |
| .msg-bubble h1,.msg-bubble h2,.msg-bubble h3 { font-weight: 600; margin: 14px 0 6px; color: var(--text); } |
| |
| |
| .cursor { |
| display: inline-block; |
| width: 2px; height: 16px; |
| background: var(--accent-2); |
| vertical-align: middle; |
| margin-left: 2px; |
| animation: blink 1s infinite; |
| border-radius: 1px; |
| } |
| @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} } |
| |
| |
| .input-area { |
| padding: 16px 24px 20px; |
| background: var(--bg); |
| border-top: 1px solid var(--border); |
| flex-shrink: 0; |
| } |
| |
| .input-inner { max-width: 740px; margin: 0 auto; } |
| |
| .input-box { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 14px; |
| padding: 14px 16px; |
| display: flex; |
| align-items: flex-end; |
| gap: 10px; |
| transition: border-color 0.15s; |
| } |
| .input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); } |
| |
| #user-input { |
| flex: 1; |
| background: none; |
| border: none; |
| color: var(--text); |
| font-family: 'Inter', sans-serif; |
| font-size: 14px; |
| line-height: 1.6; |
| resize: none; |
| outline: none; |
| max-height: 200px; |
| min-height: 24px; |
| } |
| #user-input::placeholder { color: var(--text-dim); } |
| |
| .send-btn { |
| width: 34px; height: 34px; |
| background: var(--accent); |
| border: none; |
| border-radius: 9px; |
| color: white; |
| cursor: pointer; |
| display: grid; |
| place-items: center; |
| flex-shrink: 0; |
| transition: all 0.15s; |
| font-size: 14px; |
| } |
| .send-btn:hover { background: var(--accent-2); transform: scale(1.05); } |
| .send-btn:disabled { background: var(--border); cursor: not-allowed; transform: none; } |
| |
| .input-footer { |
| display: flex; |
| justify-content: center; |
| margin-top: 8px; |
| font-size: 11px; |
| color: var(--text-dim); |
| } |
| |
| |
| ::-webkit-scrollbar { width: 5px; } |
| ::-webkit-scrollbar-track { background: transparent; } |
| ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } |
| ::-webkit-scrollbar-thumb:hover { background: var(--text-dim); } |
| |
| |
| @media (max-width: 640px) { |
| .sidebar { display: none; } |
| .starter-grid { grid-template-columns: 1fr; } |
| } |
| </style> |
| </head> |
| <body> |
|
|
| |
| <aside class="sidebar"> |
| <div class="sidebar-header"> |
| <div class="logo"> |
| <div class="logo-icon">⚡</div> |
| <div class="logo-text">Groq<span>Chat</span></div> |
| </div> |
| <button class="new-chat-btn" onclick="newChat()"> |
| <span>+</span> New conversation |
| </button> |
| </div> |
|
|
| <div class="model-section"> |
| <div class="model-label">Model</div> |
| {% for model_id, model in models.items() %} |
| <div class="model-option {% if loop.first %}active{% endif %}" data-model="{{ model_id }}" onclick="selectModel('{{ model_id }}', this)"> |
| <span class="model-icon">{{ model.icon }}</span> |
| <div class="model-info"> |
| <div class="model-name">{{ model.name }}</div> |
| <div class="model-desc">{{ model.description }}</div> |
| </div> |
| </div> |
| {% endfor %} |
| </div> |
|
|
| <div class="history-section"> |
| <div class="history-label">History</div> |
| <div id="history-list"> |
| <div class="history-empty">No conversations yet</div> |
| </div> |
| </div> |
|
|
| <div class="sidebar-footer"> |
| Powered by <a href="https://groq.com" target="_blank">Groq</a> · Meta Llama |
| </div> |
| </aside> |
|
|
| |
| <main class="main"> |
| <header class="topbar"> |
| <div class="topbar-model"> |
| <div class="dot"></div> |
| <span id="current-model-label">Llama 3.1 8B Instant</span> |
| </div> |
| <div class="topbar-actions"> |
| <button class="icon-btn" onclick="newChat()">⊕ New chat</button> |
| <button class="icon-btn" onclick="copyLastResponse()">⎘ Copy</button> |
| </div> |
| </header> |
|
|
| <div class="chat-window" id="chat-window"> |
| <div class="chat-inner" id="chat-inner"> |
| <div class="welcome" id="welcome-screen"> |
| <div class="welcome-icon">⚡</div> |
| <h1>Welcome to <span>GroqChat</span></h1> |
| <p>Ultra-fast AI conversations powered by Groq's LPU inference. Choose a model and start chatting.</p> |
| <div class="starter-grid"> |
| <button class="starter-card" onclick="sendStarter(this)"> |
| <span class="starter-icon">💡</span> |
| <div><strong>Explain a concept</strong></div> |
| <div class="starter-text">How does transformer attention work?</div> |
| </button> |
| <button class="starter-card" onclick="sendStarter(this)"> |
| <span class="starter-icon">✍️</span> |
| <div><strong>Write for me</strong></div> |
| <div class="starter-text">Draft a professional email declining a meeting</div> |
| </button> |
| <button class="starter-card" onclick="sendStarter(this)"> |
| <span class="starter-icon">🐛</span> |
| <div><strong>Debug code</strong></div> |
| <div class="starter-text">Help me find the bug in my Python code</div> |
| </button> |
| <button class="starter-card" onclick="sendStarter(this)"> |
| <span class="starter-icon">🌐</span> |
| <div><strong>Translate text</strong></div> |
| <div class="starter-text">Translate this paragraph to French</div> |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="input-area"> |
| <div class="input-inner"> |
| <div class="input-box"> |
| <textarea id="user-input" placeholder="Message GroqChat…" rows="1" onkeydown="handleKey(event)" oninput="autoResize(this)"></textarea> |
| <button class="send-btn" id="send-btn" onclick="sendMessage()"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> |
| <line x1="22" y1="2" x2="11" y2="13"></line> |
| <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon> |
| </svg> |
| </button> |
| </div> |
| <div class="input-footer">GroqChat can make mistakes. Verify important information.</div> |
| </div> |
| </div> |
| </main> |
|
|
| <script> |
| let messages = []; |
| let currentModel = "llama-3.1-8b-instant"; |
| let modelLabels = {}; |
| let conversations = []; |
| let currentConvId = null; |
| let isStreaming = false; |
| |
| |
| document.querySelectorAll('.model-option').forEach(el => { |
| const id = el.dataset.model; |
| const name = el.querySelector('.model-name').textContent.trim(); |
| modelLabels[id] = name; |
| }); |
| |
| function selectModel(id, el) { |
| currentModel = id; |
| document.querySelectorAll('.model-option').forEach(o => o.classList.remove('active')); |
| el.classList.add('active'); |
| document.getElementById('current-model-label').textContent = modelLabels[id] || id; |
| } |
| |
| function autoResize(el) { |
| el.style.height = 'auto'; |
| el.style.height = Math.min(el.scrollHeight, 200) + 'px'; |
| } |
| |
| function handleKey(e) { |
| if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } |
| } |
| |
| function sendStarter(btn) { |
| const text = btn.querySelector('.starter-text').textContent.trim(); |
| document.getElementById('user-input').value = text; |
| sendMessage(); |
| } |
| |
| function renderMarkdown(text) { |
| return text |
| .replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => |
| `<pre><code class="language-${lang}">${escHtml(code.trim())}</code></pre>`) |
| .replace(/`([^`]+)`/g, '<code>$1</code>') |
| .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') |
| .replace(/\*(.+?)\*/g, '<em>$1</em>') |
| .replace(/^### (.+)$/gm, '<h3>$1</h3>') |
| .replace(/^## (.+)$/gm, '<h2>$1</h2>') |
| .replace(/^# (.+)$/gm, '<h1>$1</h1>') |
| .replace(/^\- (.+)$/gm, '<li>$1</li>') |
| .replace(/(<li>.*<\/li>\n?)+/g, s => `<ul>${s}</ul>`) |
| .replace(/\n\n+/g, '</p><p>') |
| .replace(/^(?!<[hup])(.+)$/gm, (m) => m.startsWith('<') ? m : m) |
| .replace(/^([^<\n].+)$/gm, '$1') |
| .split('\n').filter(l => l.trim()).join('\n'); |
| } |
| |
| function escHtml(s) { |
| return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); |
| } |
| |
| function addMessage(role, content, isNew = true) { |
| const welcome = document.getElementById('welcome-screen'); |
| if (welcome) welcome.remove(); |
| |
| const inner = document.getElementById('chat-inner'); |
| const div = document.createElement('div'); |
| div.className = `message ${role}`; |
| |
| const avatarIcon = role === 'assistant' ? '⚡' : 'U'; |
| const roleLabel = role === 'assistant' ? 'GroqChat' : 'You'; |
| |
| div.innerHTML = ` |
| <div class="msg-role"> |
| <div class="msg-avatar">${avatarIcon}</div> |
| ${roleLabel} |
| </div> |
| <div class="msg-bubble" id="msg-${Date.now()}">${role === 'assistant' ? '' : escHtml(content)}</div> |
| `; |
| inner.appendChild(div); |
| scrollToBottom(); |
| return div.querySelector('.msg-bubble'); |
| } |
| |
| function scrollToBottom() { |
| const w = document.getElementById('chat-window'); |
| w.scrollTop = w.scrollHeight; |
| } |
| |
| async function sendMessage() { |
| if (isStreaming) return; |
| const input = document.getElementById('user-input'); |
| const text = input.value.trim(); |
| if (!text) return; |
| |
| input.value = ''; |
| input.style.height = 'auto'; |
| document.getElementById('send-btn').disabled = true; |
| isStreaming = true; |
| |
| messages.push({ role: 'user', content: text }); |
| addMessage('user', text); |
| |
| const assistantBubble = addMessage('assistant', ''); |
| const cursor = document.createElement('span'); |
| cursor.className = 'cursor'; |
| assistantBubble.appendChild(cursor); |
| scrollToBottom(); |
| |
| let fullText = ''; |
| |
| try { |
| const res = await fetch('/chat', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ messages, model: currentModel }) |
| }); |
| |
| const reader = res.body.getReader(); |
| const decoder = new TextDecoder(); |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| |
| const chunk = decoder.decode(value); |
| const lines = chunk.split('\n'); |
| for (const line of lines) { |
| if (line.startsWith('data: ')) { |
| const data = line.slice(6); |
| if (data === '[DONE]') break; |
| try { |
| const json = JSON.parse(data); |
| if (json.content) { |
| fullText += json.content; |
| assistantBubble.innerHTML = renderMarkdown(fullText); |
| assistantBubble.appendChild(cursor); |
| scrollToBottom(); |
| } |
| } catch {} |
| } |
| } |
| } |
| } catch (err) { |
| fullText = '⚠️ Error connecting to the API. Please check your GROQ_API_KEY.'; |
| } |
| |
| cursor.remove(); |
| assistantBubble.innerHTML = renderMarkdown(fullText) || '<em style="color:var(--text-dim)">No response.</em>'; |
| messages.push({ role: 'assistant', content: fullText }); |
| |
| |
| saveToHistory(text); |
| |
| isStreaming = false; |
| document.getElementById('send-btn').disabled = false; |
| scrollToBottom(); |
| } |
| |
| function saveToHistory(firstMessage) { |
| if (!currentConvId) { |
| currentConvId = Date.now(); |
| conversations.unshift({ id: currentConvId, title: firstMessage.slice(0, 40) + (firstMessage.length > 40 ? '…' : '') }); |
| renderHistory(); |
| } |
| } |
| |
| function renderHistory() { |
| const list = document.getElementById('history-list'); |
| if (conversations.length === 0) { |
| list.innerHTML = '<div class="history-empty">No conversations yet</div>'; |
| return; |
| } |
| list.innerHTML = conversations.map(c => |
| `<div class="history-item ${c.id === currentConvId ? 'active' : ''}" onclick="loadConv(${c.id})">${escHtml(c.title)}</div>` |
| ).join(''); |
| } |
| |
| function loadConv(id) { |
| |
| currentConvId = id; |
| renderHistory(); |
| } |
| |
| function newChat() { |
| messages = []; |
| currentConvId = null; |
| const inner = document.getElementById('chat-inner'); |
| inner.innerHTML = ` |
| <div class="welcome" id="welcome-screen"> |
| <div class="welcome-icon">⚡</div> |
| <h1>Welcome to <span>GroqChat</span></h1> |
| <p>Ultra-fast AI conversations powered by Groq's LPU inference. Choose a model and start chatting.</p> |
| <div class="starter-grid"> |
| <button class="starter-card" onclick="sendStarter(this)"> |
| <span class="starter-icon">💡</span> |
| <div><strong>Explain a concept</strong></div> |
| <div class="starter-text">How does transformer attention work?</div> |
| </button> |
| <button class="starter-card" onclick="sendStarter(this)"> |
| <span class="starter-icon">✍️</span> |
| <div><strong>Write for me</strong></div> |
| <div class="starter-text">Draft a professional email declining a meeting</div> |
| </button> |
| <button class="starter-card" onclick="sendStarter(this)"> |
| <span class="starter-icon">🐛</span> |
| <div><strong>Debug code</strong></div> |
| <div class="starter-text">Help me find the bug in my Python code</div> |
| </button> |
| <button class="starter-card" onclick="sendStarter(this)"> |
| <span class="starter-icon">🌐</span> |
| <div><strong>Translate text</strong></div> |
| <div class="starter-text">Translate this paragraph to French</div> |
| </button> |
| </div> |
| </div>`; |
| renderHistory(); |
| } |
| |
| function copyLastResponse() { |
| const bubbles = document.querySelectorAll('.message.assistant .msg-bubble'); |
| if (bubbles.length === 0) return; |
| const last = bubbles[bubbles.length - 1]; |
| navigator.clipboard.writeText(last.innerText).then(() => { |
| const btn = document.querySelector('.topbar-actions .icon-btn:last-child'); |
| const old = btn.textContent; |
| btn.textContent = '✓ Copied'; |
| setTimeout(() => btn.textContent = old, 1500); |
| }); |
| } |
| </script> |
| </body> |
| </html> |
|
|