add TOK badge to response footer + arrow key history recall

This commit is contained in:
gramps
2026-07-13 07:57:27 -07:00
parent 35af444c88
commit 12a7d92f99
2 changed files with 38 additions and 3 deletions
+6 -2
View File
@@ -46,6 +46,7 @@ def parse_llama_stream_chunk(line: str) -> tuple:
if finish == "stop":
usage = chunk.get("usage", {})
stats["tokens_per_sec"] = usage.get("tokens_per_second", 0.0)
stats["completion_tokens"] = usage.get("completion_tokens", 0)
return token, finish == "stop", stats, logprobs_list
if "message" in chunk and "content" in chunk["message"]:
token = chunk["message"]["content"]
@@ -55,6 +56,7 @@ def parse_llama_stream_chunk(line: str) -> tuple:
eval_count = chunk.get("eval_count", 0)
eval_duration = chunk.get("eval_duration", 0)
stats["tokens_per_sec"] = (eval_count / (eval_duration / 1e9)) if eval_duration > 0 else 0
stats["completion_tokens"] = eval_count
return token, done, stats, []
except json.JSONDecodeError:
pass
@@ -123,6 +125,7 @@ async def chat(request: Request):
full_response = []
all_logprobs = []
tokens_per_sec = 0.0
completion_tokens = 0
inference_base = await _get_inference_url(user_message)
if remember_response:
@@ -145,6 +148,7 @@ async def chat(request: Request):
yield f"data: {json.dumps({'token': token, 'conversation_id': conv_id})}\n\n"
if done:
tokens_per_sec = stats.get("tokens_per_sec", 0.0)
completion_tokens = stats.get("completion_tokens", 0)
assistant_msg = "".join(full_response)
perplexity = calculate_perplexity(all_logprobs) if all_logprobs else 0.0
@@ -199,7 +203,7 @@ async def chat(request: Request):
db2.commit()
db2.close()
yield f"data: {json.dumps({'done': True, 'conversation_id': conv_id, 'searched': True, 'perplexity': round(perplexity, 2), 'tokens_per_sec': round(tokens_per_sec, 1)})}\n\n"
yield f"data: {json.dumps({'done': True, 'conversation_id': conv_id, 'searched': True, 'perplexity': round(perplexity, 2), 'tokens_per_sec': round(tokens_per_sec, 1), 'tokens': completion_tokens})}\n\n"
return
saved_msg = assistant_msg
@@ -212,7 +216,7 @@ async def chat(request: Request):
db2.commit()
db2.close()
yield f"data: {json.dumps({'done': True, 'conversation_id': conv_id, 'perplexity': round(perplexity, 2), 'tokens_per_sec': round(tokens_per_sec, 1)})}\n\n"
yield f"data: {json.dumps({'done': True, 'conversation_id': conv_id, 'perplexity': round(perplexity, 2), 'tokens_per_sec': round(tokens_per_sec, 1), 'tokens': completion_tokens})}\n\n"
except httpx.RemoteProtocolError:
pass
+32 -1
View File
@@ -173,6 +173,7 @@ body { font-family: var(--font-body); background: var(--bg-primary); color: var(
.ttr-badge.high { background:rgba(231,76,60,0.15); border:1px solid rgba(231,76,60,0.3); color:var(--danger); }
.ttr-badge.search { background:rgba(230,126,34,0.15); border:1px solid rgba(230,126,34,0.3); color:#e67e22; }
.tps-badge { display:inline-block; padding:2px 8px; border-radius:10px; font-family:var(--font-mono); font-size:10px; margin-left:8px; background:rgba(72,181,224,0.15); border:1px solid rgba(72,181,224,0.3); color:var(--accent); }
.tok-badge { display:inline-block; padding:2px 8px; border-radius:10px; font-family:var(--font-mono); font-size:10px; margin-left:8px; background:rgba(52,152,219,0.15); border:1px solid rgba(52,152,219,0.3); color:#3498db; }
.msg-toolbar { display:flex; gap:2px; margin-top:8px; opacity:0; transition:opacity 0.15s; align-items:center; }
.message .content:hover .msg-toolbar { opacity:1; }
@@ -1326,6 +1327,9 @@ async function sendMessage() {
if (welcome) welcome.remove();
appendMessage('user', message, true);
conversationHistory.push({ role: 'user', content: message });
messageHistory.push(message);
if (messageHistory.length > 100) messageHistory.shift();
historyIndex = -1;
input.value = '';
input.style.height = 'auto';
updateTokenThermometer();
@@ -1394,6 +1398,7 @@ async function sendMessage() {
const roleLabel = assistantDiv.querySelector('.role-label');
if (data.searched && roleLabel) roleLabel.innerHTML += '<span class="search-badge-inline">🔍 web</span>';
if (typeof data.perplexity === 'number' && roleLabel) { const ppl = data.perplexity; roleLabel.innerHTML += `<span class="perplexity-badge ${ppl >= 15 ? 'high' : ppl >= 8 ? 'medium' : 'low'}">ppl: ${ppl.toFixed(1)}</span>`; }
if (typeof data.tokens === 'number' && data.tokens > 0 && roleLabel) roleLabel.innerHTML += `<span class="tok-badge">tok: ${data.tokens}</span>`;
if (typeof data.tokens_per_sec === 'number' && data.tokens_per_sec > 0 && roleLabel) roleLabel.innerHTML += `<span class="tps-badge">${data.tokens_per_sec.toFixed(1)} t/s</span>`;
if (roleLabel && ttr > 0) {
const ttrSec = ttr / 1000;
@@ -1567,8 +1572,34 @@ function fallbackCopy(text, btn) {
}
const userInput = document.getElementById('userInput');
const messageHistory = [];
let historyIndex = -1;
let historyDraft = '';
userInput.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 200) + 'px'; });
userInput.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } });
userInput.addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); return; }
if (e.key === 'ArrowUp') {
e.preventDefault();
if (messageHistory.length === 0) return;
if (historyIndex === -1) historyDraft = userInput.value;
if (historyIndex < messageHistory.length - 1) {
historyIndex++;
userInput.value = messageHistory[messageHistory.length - 1 - historyIndex];
}
userInput.selectionStart = userInput.selectionEnd = userInput.value.length;
userInput.dispatchEvent(new Event('input'));
return;
}
if (e.key === 'ArrowDown') {
e.preventDefault();
if (historyIndex === -1) return;
historyIndex--;
userInput.value = historyIndex === -1 ? historyDraft : messageHistory[messageHistory.length - 1 - historyIndex];
userInput.selectionStart = userInput.selectionEnd = userInput.value.length;
userInput.dispatchEvent(new Event('input'));
return;
}
});
const toastStyle = document.createElement('style');
toastStyle.textContent = '.toast-error { position:fixed; bottom:80px; left:50%; transform:translateX(-50%); background:var(--danger); color:#fff; padding:12px 24px; border-radius:var(--radius); font-family:var(--font-body); font-size:14px; z-index:9999; animation:fadeIn 0.2s; } @keyframes fadeIn { from{opacity:0;transform:translateX(-50%) translateY(10px)} to{opacity:1;transform:translateX(-50%) translateY(0)} }';
document.head.appendChild(toastStyle);