nexus-plus-v2-chat / index.html
JustScriptzz's picture
Upload folder using huggingface_hub
760f39f verified
Raw
History Blame Contribute Delete
8.63 kB
<!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 = '';
// Remove welcome
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>