forked from tinygrad/tinygrad
* chat: display reasoning_content from streamed responses The server's StreamRouter emits reasoning_content deltas for think blocks, but the chat UI was only reading delta.content, silently dropping all reasoning. Now reasoning is shown in gray (#888) and included in the message history sent back to the server. * fix
43 lines
2.4 KiB
HTML
43 lines
2.4 KiB
HTML
<!DOCTYPE html><html><head><title>tinygrad chat</title><style>
|
|
* { margin: 0 }
|
|
body { background: #212121; color: #e3e3e3; font-family: system-ui;
|
|
height: 100vh; display: flex; flex-direction: column }
|
|
#chat { flex: 1; overflow-y: auto; padding: 20px }
|
|
.msg { padding: 10px 16px; margin: 8px 0; white-space: pre-wrap; border-radius: 18px }
|
|
.user { background: #2f2f2f; margin-left: auto; width: fit-content; max-width: 70% }
|
|
#input { max-width: 768px; width: 100%; margin: 20px auto; padding: 14px 20px;
|
|
background: #2f2f2f; color: inherit; font: inherit;
|
|
border: none; outline: none; resize: none; border-radius: 24px; field-sizing: content }
|
|
</style></head><body><div id="chat"></div>
|
|
<textarea id="input" rows="1" placeholder="Ask anything" autofocus></textarea>
|
|
<script>
|
|
input.onkeydown = (e) => { if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) { e.preventDefault(); send() } }
|
|
const msgs = [];
|
|
async function send() {
|
|
if (!input.value.trim()) return;
|
|
msgs.push({role: 'user', content: input.value.trim()});
|
|
chat.innerHTML += '<div class="msg user">' + input.value.trim().replace(/</g, '<') + '</div>';
|
|
input.value = '';
|
|
const d = document.createElement('div'); d.className = 'msg'; chat.appendChild(d);
|
|
const r = await fetch('/v1/chat/completions', {method: 'POST', headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({model: 'llama', messages: msgs, stream: true, temperature: 0.7})});
|
|
let buf = '', txt = '', rsn = '';
|
|
for (const rd = r.body.getReader(), dec = new TextDecoder();;) {
|
|
const {done, value} = await rd.read();
|
|
if (done) break;
|
|
buf += dec.decode(value, {stream: true});
|
|
const lines = buf.split('\n');
|
|
buf = lines.pop();
|
|
for (const ln of lines)
|
|
if (ln.startsWith('data: ') && !ln.includes('[DONE]'))
|
|
try { const dl = JSON.parse(ln.slice(6)).choices[0]?.delta;
|
|
if (dl?.reasoning_content) { const s = document.createElement('span'); s.style.color = '#888';
|
|
s.textContent = dl.reasoning_content; rsn += dl.reasoning_content; d.appendChild(s) }
|
|
if (dl?.content) { const s = document.createElement('span');
|
|
s.textContent = dl.content; txt += dl.content; d.appendChild(s) } } catch {}
|
|
chat.scrollTop = chat.scrollHeight;
|
|
}
|
|
const m = {role:'assistant', content:txt}; if (rsn) m.reasoning_content = rsn; msgs.push(m);
|
|
}
|
|
</script></body></html>
|