Spaces:
OrbitMC
/
Runtime error

Ana-2 / templates /index.html
OrbitMC's picture
Create templates/index.html
0ca87ff verified
Raw
History Blame Contribute Delete
8.32 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🦙 LLaMA Chat</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #0f0f0f;
color: #e0e0e0;
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
padding: 16px 24px;
background: #1a1a2e;
border-bottom: 1px solid #333;
text-align: center;
}
.header h1 {
font-size: 1.4rem;
color: #a78bfa;
}
.header p {
font-size: 0.8rem;
color: #888;
margin-top: 4px;
}
.chat-container {
flex: 1;
overflow-y: auto;
padding: 20px 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
.message {
max-width: 75%;
padding: 12px 16px;
border-radius: 16px;
line-height: 1.5;
font-size: 0.95rem;
white-space: pre-wrap;
word-wrap: break-word;
}
.message.user {
align-self: flex-end;
background: #4338ca;
color: #fff;
border-bottom-right-radius: 4px;
}
.message.assistant {
align-self: flex-start;
background: #1e1e2e;
color: #d4d4d4;
border: 1px solid #333;
border-bottom-left-radius: 4px;
}
.message.assistant .typing-cursor {
display: inline-block;
width: 8px;
height: 16px;
background: #a78bfa;
animation: blink 0.7s infinite;
vertical-align: text-bottom;
margin-left: 2px;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.input-area {
padding: 16px 24px;
background: #1a1a2e;
border-top: 1px solid #333;
display: flex;
gap: 12px;
}
.input-area textarea {
flex: 1;
padding: 12px 16px;
border: 1px solid #444;
border-radius: 12px;
background: #0f0f0f;
color: #e0e0e0;
font-size: 0.95rem;
font-family: inherit;
resize: none;
outline: none;
max-height: 120px;
}
.input-area textarea:focus {
border-color: #a78bfa;
}
.input-area button {
padding: 12px 24px;
background: #7c3aed;
color: #fff;
border: none;
border-radius: 12px;
font-size: 0.95rem;
cursor: pointer;
transition: background 0.2s;
}
.input-area button:hover {
background: #6d28d9;
}
.input-area button:disabled {
background: #444;
cursor: not-allowed;
}
.status {
text-align: center;
padding: 40px;
color: #888;
}
</style>
</head>
<body>
<div class="header">
<h1>🦙 LLaMA Chat</h1>
<p>SmolLM2 1.7B Instruct • CPU • GGUF</p>
</div>
<div class="chat-container" id="chatContainer">
<div class="status" id="welcome">
Type a message below to start chatting!
</div>
</div>
<div class="input-area">
<textarea
id="userInput"
rows="1"
placeholder="Type your message..."
onkeydown="handleKey(event)"
></textarea>
<button id="sendBtn" onclick="sendMessage()">Send</button>
</div>
<script>
const chatContainer = document.getElementById('chatContainer');
const userInput = document.getElementById('userInput');
const sendBtn = document.getElementById('sendBtn');
const welcome = document.getElementById('welcome');
let isGenerating = false;
// Auto-resize textarea
userInput.addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
function handleKey(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}
function addMessage(role, text) {
if (welcome) welcome.remove();
const div = document.createElement('div');
div.className = `message ${role}`;
div.textContent = text;
chatContainer.appendChild(div);
chatContainer.scrollTop = chatContainer.scrollHeight;
return div;
}
async function sendMessage() {
const msg = userInput.value.trim();
if (!msg || isGenerating) return;
isGenerating = true;
sendBtn.disabled = true;
userInput.value = '';
userInput.style.height = 'auto';
addMessage('user', msg);
// Create assistant bubble with cursor
if (welcome) welcome.remove();
const assistantDiv = document.createElement('div');
assistantDiv.className = 'message assistant';
const cursor = document.createElement('span');
cursor.className = 'typing-cursor';
assistantDiv.appendChild(cursor);
chatContainer.appendChild(assistantDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
try {
const response = await fetch('/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: msg }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullText = '';
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop(); // keep incomplete line
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
if (data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
if (parsed.content) {
fullText += parsed.content;
assistantDiv.textContent = fullText;
// Re-add cursor
const newCursor = document.createElement('span');
newCursor.className = 'typing-cursor';
assistantDiv.appendChild(newCursor);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
if (parsed.error) {
fullText += `\n[Error: ${parsed.error}]`;
assistantDiv.textContent = fullText;
}
} catch (e) { }
}
}
}
// Remove cursor when done
assistantDiv.textContent = fullText || '(No response)';
} catch (err) {
assistantDiv.textContent = `Error: ${err.message}`;
}
isGenerating = false;
sendBtn.disabled = false;
userInput.focus();
}
</script>
</body>
</html>