| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Nexus Plus v2</title> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'); |
| * { font-family: 'Inter', sans-serif; box-sizing: border-box; } |
| body { background: #0a0a0f; color: #e1e1e6; margin: 0; min-height: 100vh; display: flex; flex-direction: column; align-items: center; } |
| .container { max-width: 680px; width: 100%; padding: 16px; display: flex; flex-direction: column; height: 100vh; } |
| .header { text-align: center; padding: 16px 0 12px; flex-shrink: 0; } |
| .header h1 { font-size: 28px; font-weight: 700; margin: 0; } |
| .header h1 span { color: #a78bfa; } |
| .header p { color: #666; font-size: 13px; margin-top: 4px; } |
| .status { display: flex; align-items: center; gap: 8px; padding: 6px 14px; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); border-radius: 20px; font-size: 12px; color: #888; width: fit-content; margin: 0 auto 12px; } |
| .dot { width: 6px; height: 6px; border-radius: 50%; background: #22c55e; flex-shrink: 0; } |
| .dot.loading { background: #eab308; animation: pulse 1s infinite; } |
| .dot.error { background: #ef4444; } |
| @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} } |
| .chat-box { flex: 1; overflow-y: auto; padding: 8px 0; } |
| .msg { padding: 12px 16px; margin: 8px 0; border-radius: 16px; max-width: 85%; line-height: 1.6; font-size: 15px; white-space: pre-wrap; word-wrap: break-word; } |
| .msg-user { background: linear-gradient(135deg, #7c3aed22, #6d28d922); border: 1px solid #7c3aed33; margin-left: auto; } |
| .msg-bot { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); } |
| .typing { display: inline-flex; gap: 4px; padding: 8px 14px; background: rgba(255,255,255,0.04); border-radius: 12px; margin: 8px 0; } |
| .typing i { width: 7px; height: 7px; background: #7c3aed; border-radius: 50%; animation: bounce 1.4s infinite; display: inline-block; } |
| .typing i:nth-child(2) { animation-delay: 0.16s; } |
| .typing i:nth-child(3) { animation-delay: 0.32s; } |
| @keyframes bounce { 0%,60%,100%{transform:translateY(0);opacity:0.4} 30%{transform:translateY(-6px);opacity:1} } |
| .input-row { display: flex; gap: 8px; padding: 12px 0; flex-shrink: 0; } |
| .input-row input { flex: 1; background: #111118; border: 1px solid rgba(255,255,255,0.1); color: #fff; border-radius: 16px; padding: 12px 16px; font-size: 15px; outline: none; } |
| .input-row input:focus { box-shadow: 0 0 0 2px #7c3aed44; } |
| .input-row button { background: #7c3aed; color: #fff; border: none; border-radius: 16px; padding: 12px 24px; font-weight: 600; cursor: pointer; flex-shrink: 0; } |
| .input-row button:hover { background: #6d28d9; } |
| .input-row button:disabled { opacity: 0.5; cursor: not-allowed; } |
| .welcome { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); border-radius: 16px; padding: 16px; margin: 8px 0; text-align: center; color: #888; font-size: 14px; } |
| .links { text-align: center; padding: 8px 0; flex-shrink: 0; } |
| .links a { color: #555; font-size: 12px; text-decoration: none; } |
| .links a:hover { color: #888; } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <div class="header"> |
| <h1>Nexus <span>Plus</span> <span style="color:#555;font-size:18px;font-weight:400">v2</span></h1> |
| <p>Qwen3-4B fine-tuned with QLoRA</p> |
| </div> |
| <div id="tokenBar" style="display:flex;gap:8px;margin-bottom:8px;flex-shrink:0;"> |
| <input type="password" id="tokenInput" placeholder="HF token (hf_...)" style="flex:1;background:#111118;border:1px solid rgba(255,255,255,0.1);color:#fff;border-radius:16px;padding:10px 16px;font-size:13px;outline:none;"> |
| <button onclick="saveToken()" style="background:#7c3aed;color:#fff;border:none;border-radius:16px;padding:10px 16px;font-size:13px;cursor:pointer;">Save</button> |
| </div> |
| <div class="status"><div class="dot" id="statusDot"></div><span id="statusText">Ready</span></div> |
| <div class="chat-box" id="chatBox"> |
| <div class="welcome"> |
| <strong>Hello!</strong> I'm Nexus Plus v2 — powered by Qwen3-4B fine-tuned with QLoRA.<br>Ask me anything. |
| </div> |
| </div> |
| <div class="input-row"> |
| <input type="text" id="userInput" placeholder="Type a message..." autofocus> |
| <button id="sendBtn" onclick="sendMessage()">Send</button> |
| </div> |
| <div class="links"> |
| <a href="https://huggingface.co/JustScriptzz/nexus-plus-v2">Model</a> · |
| <a href="https://github.com/JustScriptzz/nexus-smAll-web">GitHub</a> · |
| <a href="https://justscriptzz-nexus-smAll-v1-chat.hf.space">SmAll v1</a> |
| </div> |
| </div> |
|
|
| <script> |
| const chatBox = document.getElementById('chatBox'); |
| const userInput = document.getElementById('userInput'); |
| const sendBtn = document.getElementById('sendBtn'); |
| const statusDot = document.getElementById('statusDot'); |
| const statusText = document.getElementById('statusText'); |
| const HISTORY = []; |
| let generating = false; |
| let HF_TOKEN = localStorage.getItem('hf_token') || ''; |
| |
| if (HF_TOKEN) { |
| document.getElementById('tokenInput').value = HF_TOKEN; |
| document.getElementById('tokenBar').style.display = 'none'; |
| } |
| |
| function saveToken() { |
| HF_TOKEN = document.getElementById('tokenInput').value.trim(); |
| if (HF_TOKEN) { |
| localStorage.setItem('hf_token', HF_TOKEN); |
| document.getElementById('tokenBar').style.display = 'none'; |
| } |
| } |
| |
| function setStatus(state, text) { |
| statusDot.className = 'dot' + (state === 'loading' ? ' loading' : state === 'error' ? ' error' : ''); |
| statusText.textContent = text; |
| } |
| |
| function addMsg(role, text) { |
| const div = document.createElement('div'); |
| div.className = 'msg ' + (role === 'user' ? 'msg-user' : 'msg-bot'); |
| div.textContent = text; |
| chatBox.appendChild(div); |
| chatBox.scrollTop = chatBox.scrollHeight; |
| return div; |
| } |
| |
| function addTyping() { |
| const div = document.createElement('div'); |
| div.className = 'typing'; |
| div.id = 'typing'; |
| div.innerHTML = '<i></i><i></i><i></i>'; |
| chatBox.appendChild(div); |
| chatBox.scrollTop = chatBox.scrollHeight; |
| } |
| |
| function removeTyping() { |
| const t = document.getElementById('typing'); |
| if (t) t.remove(); |
| } |
| |
| async function sendMessage() { |
| const msg = userInput.value.trim(); |
| if (!msg || generating) return; |
| if (!HF_TOKEN) { |
| document.getElementById('tokenBar').style.display = 'flex'; |
| document.getElementById('tokenInput').focus(); |
| return; |
| } |
| generating = true; |
| sendBtn.disabled = true; |
| userInput.value = ''; |
| |
| |
| const w = chatBox.querySelector('.welcome'); |
| if (w) w.remove(); |
| |
| addMsg('user', msg); |
| HISTORY.push({ role: 'user', content: msg }); |
| addTyping(); |
| setStatus('loading', 'Generating...'); |
| |
| try { |
| const resp = await fetch('https://router.huggingface.co/hf-inference/models/JustScriptzz/nexus-plus-v2/v1/chat/completions', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| 'Authorization': 'Bearer ' + HF_TOKEN |
| }, |
| body: JSON.stringify({ |
| model: 'JustScriptzz/nexus-plus-v2', |
| messages: HISTORY, |
| max_tokens: 512, |
| temperature: 0.7, |
| top_p: 0.8, |
| stream: true |
| }) |
| }); |
| |
| if (!resp.ok) { |
| const err = await resp.text(); |
| throw new Error(`API error ${resp.status}: ${err}`); |
| } |
| |
| removeTyping(); |
| const botDiv = addMsg('bot', ''); |
| const reader = resp.body.getReader(); |
| const decoder = new TextDecoder(); |
| let full = ''; |
| 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: ')) { |
| const data = line.slice(6); |
| if (data === '[DONE]') continue; |
| try { |
| const j = JSON.parse(data); |
| const delta = j.choices?.[0]?.delta?.content; |
| if (delta) { |
| full += delta; |
| botDiv.textContent = full; |
| chatBox.scrollTop = chatBox.scrollHeight; |
| } |
| } catch(e) {} |
| } |
| } |
| } |
| |
| HISTORY.push({ role: 'assistant', content: full }); |
| setStatus('ready', 'Ready'); |
| } catch (e) { |
| removeTyping(); |
| addMsg('bot', 'Error: ' + e.message); |
| setStatus('error', 'Error — try again'); |
| } |
| |
| generating = false; |
| sendBtn.disabled = false; |
| userInput.focus(); |
| } |
| |
| userInput.addEventListener('keydown', e => { |
| if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } |
| }); |
| </script> |
| </body> |
| </html> |
|
|