fastsdcpu / static /index.html
Gaston895's picture
fix: lower token limits for CPU, streaming UI with multi-turn history, /chat instead of /ask
251fc50
Raw History Blame Contribute Delete
6.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LFM2-700M Chat</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, "Segoe UI", system-ui, sans-serif;
background: #f7f8fa;
color: #1f2328;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px;
}
h1 {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 16px;
color: #1f2328;
}
h1 span { color: #3b82d4; }
#chat-box {
width: 100%;
max-width: 720px;
flex: 1;
overflow-y: auto;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 16px;
}
.msg {
max-width: 85%;
padding: 10px 14px;
border-radius: 10px;
line-height: 1.6;
font-size: 0.93rem;
white-space: pre-wrap;
word-break: break-word;
}
.msg.user {
align-self: flex-end;
background: #3b82d4;
color: #fff;
border-bottom-right-radius: 2px;
}
.msg.assistant {
align-self: flex-start;
background: #f0f2f5;
color: #1f2328;
border-bottom-left-radius: 2px;
}
.msg.thinking {
align-self: flex-start;
background: #f0f2f5;
color: #57606a;
font-style: italic;
animation: pulse 1.2s ease-in-out infinite;
}
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
#input-row {
display: flex;
width: 100%;
max-width: 720px;
gap: 8px;
}
#question {
flex: 1;
padding: 10px 14px;
font-size: 0.95rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
outline: none;
background: #fff;
resize: none;
height: 44px;
line-height: 1.4;
font-family: inherit;
}
#question:focus { border-color: #3b82d4; }
#send-btn {
padding: 0 20px;
height: 44px;
background: #3b82d4;
color: #fff;
border: none;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
}
#send-btn:disabled { background: #a5c4ef; cursor: not-allowed; }
#send-btn:hover:not(:disabled) { background: #2b6cbf; }
#settings {
display: flex;
gap: 12px;
width: 100%;
max-width: 720px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.setting {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.82rem;
color: #57606a;
}
.setting input[type=range] { width: 80px; }
.setting span { min-width: 28px; text-align: right; font-weight: 500; color: #1f2328; }
</style>
</head>
<body>
<h1>🤖 <span>LFM2-700M</span> Chat</h1>
<div id="settings">
<div class="setting">
Max tokens
<input type="range" id="max_tokens" min="64" max="1024" step="64" value="256"
oninput="document.getElementById('mt_val').textContent=this.value">
<span id="mt_val">256</span>
</div>
<div class="setting">
Temperature
<input type="range" id="temperature" min="0.01" max="1.5" step="0.01" value="0.3"
oninput="document.getElementById('temp_val').textContent=parseFloat(this.value).toFixed(2)">
<span id="temp_val">0.30</span>
</div>
<div class="setting">
Min-P
<input type="range" id="min_p" min="0" max="1" step="0.01" value="0.15"
oninput="document.getElementById('mp_val').textContent=parseFloat(this.value).toFixed(2)">
<span id="mp_val">0.15</span>
</div>
</div>
<div id="chat-box"></div>
<div id="input-row">
<textarea id="question" placeholder="Ask anything…" rows="1"></textarea>
<button id="send-btn" onclick="sendQuestion()">Send</button>
</div>
<script>
const API = ''; // same origin — served by FastAPI
// Maintain conversation history for multi-turn chat
const history = [];
const chatBox = document.getElementById('chat-box');
const input = document.getElementById('question');
const sendBtn = document.getElementById('send-btn');
// Send on Enter (Shift+Enter = newline)
input.addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendQuestion(); }
});
function appendMsg(role, text) {
const div = document.createElement('div');
div.className = `msg ${role}`;
div.textContent = text;
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
return div;
}
async function sendQuestion() {
const q = input.value.trim();
if (!q) return;
input.value = '';
sendBtn.disabled = true;
// Add user turn to history and UI
history.push({ role: 'user', content: q });
appendMsg('user', q);
const replyDiv = appendMsg('thinking', '▍');
const body = {
messages: history.slice(),
max_new_tokens: parseInt(document.getElementById('max_tokens').value),
temperature: parseFloat(document.getElementById('temperature').value),
min_p: parseFloat(document.getElementById('min_p').value),
repetition_penalty: 1.05,
stream: true
};
let fullText = '';
try {
const res = await fetch(`${API}/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!res.ok) {
const err = await res.text();
replyDiv.className = 'msg assistant';
replyDiv.textContent = `Error ${res.status}: ${err}`;
} else {
// Stream tokens as they arrive
const reader = res.body.getReader();
const decoder = new TextDecoder();
replyDiv.className = 'msg assistant';
replyDiv.textContent = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
fullText += decoder.decode(value, { stream: true });
replyDiv.textContent = fullText;
chatBox.scrollTop = chatBox.scrollHeight;
}
// Store assistant reply in history for multi-turn context
history.push({ role: 'assistant', content: fullText });
}
} catch (err) {
replyDiv.className = 'msg assistant';
replyDiv.textContent = `Network error: ${err.message}`;
}
sendBtn.disabled = false;
input.focus();
chatBox.scrollTop = chatBox.scrollHeight;
}
</script>
</body>
</html>