File size: 12,312 Bytes
379ab55 a1ae561 379ab55 a1ae561 379ab55 35f4ea6 379ab55 7434dde 32cb8a0 7434dde 32cb8a0 7434dde 32cb8a0 d6c22f2 32cb8a0 a1ae561 35f4ea6 379ab55 d6c22f2 379ab55 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPU Mart Assistant — voice + text AI for RDP GPU Mart</title>
<meta name="description" content="Ask anything about RDP GPU Mart (rdp.in/gpu-mart) — India's datacenter-grade GPU marketplace. A small fine-tuned model, running right in your browser, with voice and text.">
<style>
:root { --red:#d82228; --bg:#e8e8e8; --ink:#1a1a1a; --card:#ffffff; }
* { box-sizing:border-box; margin:0; padding:0; }
body { font-family:'Segoe UI',system-ui,-apple-system,sans-serif; background:var(--bg); color:var(--ink);
display:flex; flex-direction:column; align-items:center; min-height:100vh; }
header { width:100%; background:#fff; border-bottom:3px solid var(--red); padding:14px 20px; text-align:center; }
header h1 { font-size:1.25rem; } header h1 span { color:var(--red); }
header p { font-size:.85rem; color:#555; margin-top:2px; }
#status { width:min(760px,94vw); margin:10px auto 0; font-size:.85rem; color:#444; background:#fff;
border-radius:10px; padding:8px 14px; border-left:4px solid var(--red); }
#bar { height:6px; background:#eee; border-radius:3px; margin-top:6px; overflow:hidden; display:none; }
#bar > div { height:100%; width:0%; background:var(--red); transition:width .3s; }
#chat { width:min(760px,94vw); flex:1; margin:12px auto; background:var(--card); border-radius:14px;
padding:16px; overflow-y:auto; min-height:340px; max-height:56vh; box-shadow:0 1px 4px rgba(0,0,0,.08); }
.msg { margin:8px 0; padding:10px 14px; border-radius:12px; max-width:85%; line-height:1.45; font-size:.95rem;
white-space:pre-wrap; word-wrap:break-word; }
.user { background:var(--red); color:#fff; margin-left:auto; }
.bot { background:#f2f2f2; }
#controls { width:min(760px,94vw); margin:0 auto 8px; display:flex; gap:8px; }
#box { flex:1; padding:12px 14px; border:1px solid #ccc; border-radius:10px; font-size:1rem; outline:none; }
#box:focus { border-color:var(--red); }
button { border:none; border-radius:10px; padding:12px 18px; font-size:1rem; cursor:pointer; }
#send { background:var(--red); color:#fff; }
#mic { background:#fff; color:var(--red); border:2px solid var(--red); }
#mic.listening { background:var(--red); color:#fff; animation:pulse 1.2s infinite; }
@keyframes pulse { 50% { opacity:.6; } }
#opts { width:min(760px,94vw); margin:0 auto 10px; display:flex; align-items:center; gap:14px; font-size:.85rem; color:#444; flex-wrap:wrap; }
.chip { background:#fff; border:1px solid #ddd; border-radius:16px; padding:5px 12px; cursor:pointer; font-size:.8rem; }
.chip:hover { border-color:var(--red); color:var(--red); }
footer { width:100%; text-align:center; font-size:.75rem; color:#777; padding:10px; }
footer a { color:var(--red); text-decoration:none; }
</style>
</head>
<body>
<header>
<h1>🖥️ GPU Mart <span>Assistant</span></h1>
<p>India's datacenter-grade GPU marketplace — ask by typing or speaking. Runs entirely in your browser.</p>
</header>
<div id="status">⏳ Loading the GPU Mart model…<div id="bar"><div></div></div></div>
<div id="chat"></div>
<div id="opts">
<label><input type="checkbox" id="speakToggle" checked> 🔊 Speak replies</label>
<span class="chip" data-q="What is GPU Mart?">What is GPU Mart?</span>
<span class="chip" data-q="Explain CARINA, QUASAR and DRACO">CARINA / QUASAR / DRACO</span>
<span class="chip" data-q="Price of the DRACO 8x H200 server?">DRACO 8× H200 price</span>
<span class="chip" data-q="How do I get a quote?">Get a quote</span>
</div>
<div id="controls">
<input id="box" type="text" placeholder="Ask about GPU Mart…" autocomplete="off">
<button id="mic" title="Speak your question">🎤</button>
<button id="send">Send</button>
</div>
<footer>
Model: <a href="https://huggingface.co/VickyRDP/gpumart-qwen2.5-0.5b-instruct" target="_blank">gpumart fine-tune</a> ·
Store: <a href="https://rdp.in/gpu-mart/" target="_blank">rdp.in/gpu-mart</a> ·
Sales: +91 720 794 8743
</footer>
<script type="module">
const MODEL_ID = 'VickyRDP/gpumart-qwen2.5-0.5b-instruct';
const SYSTEM = "You are the GPU Mart assistant for RDP GPU Mart (rdp.in/gpu-mart), India's datacenter-grade GPU marketplace by RDP Computers India Private Limited. Answer questions about GPU Mart products, series, pricing, policies and support accurately and concisely.";
const chatEl = document.getElementById('chat');
const statusEl = document.getElementById('status');
const barWrap = document.getElementById('bar');
const bar = barWrap.querySelector('div');
const box = document.getElementById('box');
const sendBtn = document.getElementById('send');
const micBtn = document.getElementById('mic');
const speakToggle = document.getElementById('speakToggle');
const history = [];
let generator = null, busy = false;
function addMsg(text, cls) {
const d = document.createElement('div');
d.className = 'msg ' + cls;
d.textContent = text;
chatEl.appendChild(d);
chatEl.scrollTop = chatEl.scrollHeight;
return d;
}
function speak(text) {
if (!speakToggle.checked || !window.speechSynthesis) return;
speechSynnthesis.cancel();
const u = new SpeechSynthesisUtterance(text.slice(0, 800));
u.lang = 'en-IN'; u.rate = 1.02;
const vs = speechSynthesis.getVoices();
u.voice = vs.find(v => v.lang === 'en-IN') || vs.find(v => v.lang.startsWith('en')) || null;
speechSynthesis.speak(u);
}
async function loadModel() {
try {
const { pipeline, TextStreamer } = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1');
window.__TextStreamer = TextStreamer;
// Refresh cached small config files (config/tokenizer/generation) so config fixes
// reach returning visitors; the big ONNX weights stay cached.
try {
const c = await caches.open('transformers-cache');
for (const req of await c.keys()) {
if (req.url.includes('gpumart-qwen2.5-0.5b-instruct') && /(config\.json|generation_config\.json|tokenizer_config\.json)$/.test(req.url)) {
await c.delete(req);
}
}
} catch (e) {}
// WASM (CPU) inference on purpose: this fp16 export is numerically stable on the
// WASM backend (verified), while WebGPU shader-f16 execution corrupts the output.
// Do NOT switch this to 'webgpu' without re-exporting a WebGPU-safe ONNX first.
const device = 'wasm';
statusEl.firstChild.textContent = '⏳ Downloading the GPU Mart model (~1.2 GB, one-time — cached for future visits)…';
barWrap.style.display = 'block';
const files = {};
generator = await pipeline('text-generation', MODEL_ID, {
device, dtype: 'fp16',
session_options: { graphOptimizationLevel: 'disabled' },
progress_callback: p => {
if (p.status === 'progress' && p.total) {
files[p.file] = [p.loaded, p.total];
let l = 0, t = 0; for (const k in files) { l += files[k][0]; t += files[k][1]; }
bar.style.width = Math.round(100 * l / t) + '%';
}
}
});
// ---- KV dtype adapter: different ONNX Runtime builds resolve this fp16 graph
// to fp16 or fp32 KV-cache inputs. Learn the expected dtypes by try/flip/retry on
// the first run, then convert feeds to match (verified in both directions).
try {
const HasF16 = typeof Float16Array !== 'undefined';
for (const sess of Object.values(generator.model.sessions || {})) {
const orig = sess.run.bind(sess);
let want = null;
sess.run = async (feeds, opts) => {
let Tctor = null;
for (const k in feeds) if (feeds[k] && feeds[k].constructor) { Tctor = feeds[k].constructor; break; }
const conv = (t, ty) => {
if (ty === 'float16') { if (!HasF16) return t; return new Tctor('float16', Float16Array.from(t.data), t.dims); }
return new Tctor('float32', Float32Array.from(t.data), t.dims);
};
const isF = t => t && (t.type === 'float32' || t.type === 'float16');
if (want) {
for (const k in feeds) { const t = feeds[k], w = want[k];
if (isF(t) && w && t.type !== w) feeds[k] = conv(t, w); }
return orig(feeds, opts);
}
try {
const out = await orig(feeds, opts);
want = {}; for (const k in feeds) if (feeds[k] && feeds[k].type) want[k] = feeds[k].type;
return out;
} catch (e) {
if (!/input data type/i.test(String(e && e.message || e)) || !Tctor) throw e;
console.log('[GM] dtype mismatch — flipping float feeds and retrying');
for (const k in feeds) { const t = feeds[k];
if (isF(t)) feeds[k] = conv(t, t.type === 'float32' ? 'float16' : 'float32'); }
const out = await orig(feeds, opts);
want = {}; for (const k in feeds) if (feeds[k] && feeds[k].type) want[k] = feeds[k].type;
return out;
}
};
}
console.log('[GM] kv dtype adapter installed');
} catch (e) { console.warn('[GM] kv dtype adapter install failed', e); }
barWrap.style.display = 'none';
statusEl.firstChild.textContent = '🟢 GPU Mart model loaded — ask away (type or tap the mic). Replies stream in as they are generated.';
addMsg("Namaste! I'm the GPU Mart assistant — trained on RDP GPU Mart's catalog and policies. Ask me about products, CARINA/QUASAR/DRACO series, pricing, quotes, shipping, warranty or support.", 'bot');
} catch (e) {
console.error(e);
statusEl.firstChild.textContent = '🔴 Could not load the model in this browser. Please use the latest Chrome or Edge. Meanwhile, visit rdp.in/gpu-mart or call +91 720 794 8743.';
}
}
async function respond(text) {
if (!text.trim() || busy) return;
box.value = '';
addMsg(text, 'user');
if (!generator) { addMsg('The model is still loading — one moment please…', 'bot'); return; }
busy = true; sendBtn.disabled = true;
const botEl = addMsg('…', 'bot');
try {
const msgs = [{ role: 'system', content: SYSTEM }, ...history.slice(-6), { role: 'user', content: text }];
let out = '';
const streamer = new window.__TextStreamer(generator.tokenizer, {
skip_prompt: true, skip_special_tokens: true,
callback_function: t => { out += t; botEl.textContent = out; chatEl.scrollTop = chatEl.scrollHeight; }
});
const res = await generator(msgs, { max_new_tokens: 160, do_sample: false, repetition_penalty: 1.1, streamer });
const full = (res[0].generated_text.at(-1).content || out).trim();
botEl.textContent = full;
history.push({ role: 'user', content: text }, { role: 'assistant', content: full });
speak(full);
} catch (e) {
console.error(e);
botEl.textContent = 'Sorry, something went wrong generating a reply. Please try again.';
}
busy = false; sendBtn.disabled = false;
}
sendBtn.onclick = () => respond(box.value);
box.addEventListener('keydown', e => { if (e.key === 'Enter') respond(box.value); });
document.querySelectorAll('.chip').forEach(c => c.onclick = () => respond(c.dataset.q));
// ---- Voice input (Web Speech API) ----
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SR) { micBtn.disabled = true; micBtn.title = 'Voice input not supported in this browser'; }
let rec = null, listening = false;
micBtn.onclick = () => {
if (!SR) return;
if (listening) { rec && rec.stop(); return; }
rec = new SR();
rec.lang = 'en-IN'; rec.interimResults = true;
let finalTxt = '';
rec.onresult = e => { let t = ''; for (const r of e.results) t += r[0].transcript; box.value = t;
if (e.results[e.results.length - 1].isFinal) finalTxt = t; };
rec.onend = () => { listening = false; micBtn.classList.remove('listening'); micBtn.textContent = '🎤';
if (finalTxt.trim()) respond(finalTxt); };
rec.onerror = () => { listening = false; micBtn.classList.remove('listening'); micBtn.textContent = '🎤'; };
listening = true; micBtn.classList.add('listening'); micBtn.textContent = '🔴';
rec.start();
};
if (window.speechSynthesis) speechSynthesis.getVoices();
loadModel();
</script>
</body>
</html>
|