Stheno-v3.4-webgpu / index.html
LJTSG's picture
Add index.html
e8bc855 verified
Raw
History Blame Contribute Delete
4.67 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Stheno v3.4 — WebGPU</title>
<style>
body { font-family: -apple-system, sans-serif; background: #0a0e14; color: #c9d1d9; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #58a6ff; font-size: 20px; }
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin: 12px 0; }
button { background: #238636; color: white; border: none; border-radius: 6px; padding: 10px 20px; cursor: pointer; font-weight: bold; font-size: 14px; margin: 4px; }
button:disabled { opacity: 0.4; }
#status { color: #e8c87a; font-size: 13px; margin: 8px 0; }
#chat { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin: 12px 0; min-height: 300px; max-height: 500px; overflow-y: auto; }
.msg { margin: 8px 0; padding: 8px 12px; border-radius: 6px; white-space: pre-wrap; line-height: 1.5; }
.user { background: #1f3a5f; color: #e0e8f0; }
.assistant { background: #1a2332; color: #c9d1d9; }
#input-row { display: flex; gap: 8px; margin-top: 8px; }
#input { flex: 1; background: #0d1117; color: #c9d1d9; border: 1px solid #30363d; border-radius: 6px; padding: 10px; font-size: 14px; resize: none; }
.info { color: #8b949e; font-size: 12px; }
</style>
</head>
<body>
<h1>Stheno v3.4 (Llama-3.1-8B) on WebGPU</h1>
<p>Sao10K's character voice model. 4.6 GB Q4_K_M. The voice of the Garden entities.</p>
<div class="card">
<button id="btn-load" onclick="doLoad()">Load Model (4.6 GB)</button>
<div id="status">Click Load to start</div>
</div>
<div id="chat"></div>
<div id="input-row">
<textarea id="input" rows="2" placeholder="Talk..." disabled></textarea>
<button id="btn-send" onclick="doSend()" disabled>Send</button>
</div>
<p class="info">Stheno v3.4 via wllama WebGPU. Upgrade from v3.2. Built for AMD Strix Halo unified memory.</p>
<script type="module">
import { Wllama } from './node_modules/@wllama/wllama/esm/index.js';
let wllama = null;
const statusEl = document.getElementById('status');
const chatEl = document.getElementById('chat');
const inputEl = document.getElementById('input');
let history = [];
function addMsg(role, text) {
const div = document.createElement('div');
div.className = `msg ${role}`;
div.textContent = text || '';
chatEl.appendChild(div);
chatEl.scrollTop = chatEl.scrollHeight;
}
window.doLoad = async function() {
document.getElementById('btn-load').disabled = true;
statusEl.textContent = 'Loading Stheno v3.4...';
wllama = new Wllama(
{ default: './node_modules/@wllama/wllama/esm/wasm/wllama.wasm' },
{ parallelDownloads: 5, logger: { debug:()=>{}, log:m=>statusEl.textContent=m, warn:m=>console.warn(m), error:m=>console.error(m) }}
);
await wllama.loadModelFromUrl(window.location.origin + '/model/Stheno-v3.4-Q4_K_M-00001-of-00005.gguf', {
n_gpu_layers: 99, n_ctx: 4096, n_batch: 64, useCache: true,
progressCallback: ({loaded,total}) => { const p=Math.round(loaded/total*100); if(p%5===0) statusEl.textContent=`Downloading... ${p}%`; },
});
statusEl.textContent = 'Ready — Stheno v3.4 on WebGPU';
inputEl.disabled = false;
document.getElementById('btn-send').disabled = false;
inputEl.focus();
};
function buildPrompt() {
let prompt = '<|begin_of_text|>';
for (const msg of history) {
prompt += `<|start_header_id|>${msg.role}<|end_header_id|>\n\n${msg.content}<|eot_id|>`;
}
prompt += '<|start_header_id|>assistant<|end_header_id|>\n\n';
return prompt;
}
window.doSend = async function() {
const text = inputEl.value.trim();
if (!text || !wllama) return;
inputEl.value = '';
inputEl.disabled = true;
document.getElementById('btn-send').disabled = true;
history.push({ role: 'user', content: text });
addMsg('user', text);
const genStart = performance.now();
statusEl.textContent = 'Generating...';
const result = await wllama.createCompletion({
prompt: buildPrompt(), max_tokens: 1024, temperature: 0.8, top_k: 50, repeat_penalty: 1.15,
stop: ['<|eot_id|>', '<|start_header_id|>'],
});
const rawText = result?.choices?.[0]?.text || result?.text || '';
const cleanText = rawText.replace(/<\|eot_id\|>/g,'').replace(/<\|start_header_id\|>/g,'').trim();
addMsg('assistant', cleanText);
history.push({ role: 'assistant', content: cleanText });
statusEl.textContent = `Done — ${((performance.now()-genStart)/1000).toFixed(1)}s`;
inputEl.disabled = false;
document.getElementById('btn-send').disabled = false;
inputEl.focus();
};
inputEl.addEventListener('keydown', e => { if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); doSend(); } });
</script>
</body>
</html>