KAI-STUDIO-ROUTER / static /index.html
StrawberryJelly's picture
Create static/index.html
70244d4 verified
Raw
History Blame Contribute Delete
5.92 kB
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KAI STUDIO</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #0a0a0a; color: #e0e0e0; height: 100vh; display: flex; flex-direction: column;
}
header {
padding: 12px 20px; border-bottom: 1px solid #222; display: flex;
justify-content: space-between; align-items: center; background: #111;
}
.logo { font-weight: 700; font-size: 18px; color: #4ade80; }
.model-badge {
font-size: 12px; padding: 4px 8px; border-radius: 6px;
background: #1f2937; color: #9ca3af;
}
.model-badge.qwen { background: #3b82f6; color: white; }
.model-badge.mistral { background: #8b5cf6; color: white; }
#chat {
flex: 1; overflow-y: auto; padding: 20px; display: flex;
flex-direction: column; gap: 16px;
}
.msg { max-width: 80%; padding: 12px 16px; border-radius: 12px; line-height: 1.5; }
.msg.user { background: #1e40af; align-self: flex-end; }
.msg.assistant { background: #1f2937; align-self: flex-start; }
.msg pre { background: #0a0a0a; padding: 12px; border-radius: 8px; overflow-x: auto; margin: 8px 0; }
.msg code { font-family: 'Courier New', monospace; }
footer { padding: 16px; border-top: 1px solid #222; background: #111; }
.input-wrap { display: flex; gap: 8px; }
#input {
flex: 1; padding: 12px; border-radius: 8px; border: 1px solid #333;
background: #0a0a0a; color: #e0e0e0; font-size: 16px; resize: none;
}
#send {
padding: 0 20px; border-radius: 8px; border: none;
background: #4ade80; color: #0a0a0a; font-weight: 600; cursor: pointer;
}
#send:disabled { opacity: 0.5; cursor: not-allowed; }
.typing { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #9ca3af; animation: blink 1s infinite; }
@keyframes blink { 50% { opacity: 0; } }
</style>
</head>
<body>
<header>
<div class="logo">KAI STUDIO</div>
<div id="modelBadge" class="model-badge">Auto</div>
</header>
<div id="chat"></div>
<footer>
<div class="input-wrap">
<textarea id="input" rows="1" placeholder="Napisz wiadomość... Shift+Enter = nowa linia"></textarea>
<button id="send">Wyślij</button>
</div>
</footer>
<script>
const chat = document.getElementById('chat');
const input = document.getElementById('input');
const send = document.getElementById('send');
const modelBadge = document.getElementById('modelBadge');
let messages = [];
let isStreaming = false;
// Auto-resize textarea
input.addEventListener('input', () => {
input.style.height = 'auto';
input.style.height = input.scrollHeight + 'px';
});
// Send on Enter, new line on Shift+Enter
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' &&!e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
send.onclick = sendMessage;
function addMessage(role, content) {
const div = document.createElement('div');
div.className = `msg ${role}`;
div.innerHTML = content.replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
chat.appendChild(div);
chat.scrollTop = chat.scrollHeight;
return div;
}
async function sendMessage() {
const text = input.value.trim();
if (!text || isStreaming) return;
input.value = '';
input.style.height = 'auto';
addMessage('user', text);
messages.push({role: 'user', content: text});
isStreaming = true;
send.disabled = true;
const assistantDiv = addMessage('assistant', '<span class="typing"></span>');
let fullResponse = '';
let currentModel = 'auto';
try {
const res = await fetch('/v1/chat/completions', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
messages: messages,
stream: true,
max_tokens: 600
})
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const {done, value} = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith(': x-kai-model-used ')) {
currentModel = line.split(' ')[2];
modelBadge.textContent = currentModel.toUpperCase();
modelBadge.className = `model-badge ${currentModel}`;
}
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;
try {
const json = JSON.parse(data);
const delta = json.choices[0]?.delta?.content || '';
fullResponse += delta;
assistantDiv.innerHTML = fullResponse.replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
chat.scrollTop = chat.scrollHeight;
} catch (e) {}
}
}
}
messages.push({role: 'assistant', content: fullResponse});
} catch (err) {
assistantDiv.innerHTML = `Error: ${err.message}`;
} finally {
isStreaming = false;
send.disabled = false;
input.focus();
}
}
// Focus on load
input.focus();
</script>
</body>
</html>