Download index.html from Aigenthix/Comp_App: direct link, hf CLI and curl.
- Browser
- Download file 23.1 kB
-
https://huggingface.co/spaces/Aigenthix/Comp_App/resolve/main/index.html
- Command line
-
hf download hf://spaces/Aigenthix/Comp_App/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Aigenthix/Comp_App/resolve/main/index.html
23.1 kB
| <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 ── */ | |
| .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 selector */ | |
| .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); } | |
| /* Conversation history */ | |
| .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 */ | |
| .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 ── */ | |
| .main { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| background: var(--bg); | |
| } | |
| /* Top bar */ | |
| .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 */ | |
| .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 */ | |
| .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); } | |
| /* Messages */ | |
| .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); } | |
| /* Typing cursor */ | |
| .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 */ | |
| .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); | |
| } | |
| /* Scrollbar */ | |
| ::-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); } | |
| /* Mobile */ | |
| @media (max-width: 640px) { | |
| .sidebar { display: none; } | |
| .starter-grid { grid-template-columns: 1fr; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Sidebar --> | |
| <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 --> | |
| <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; | |
| // Gather model labels from DOM | |
| 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 }); | |
| // Save to history | |
| 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) { | |
| // For simplicity, just highlight; full persistence would need backend | |
| 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> | |