<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Genius Chat</title>
<style>
:root {
--bg: #0a0a0c;
--surface: #131317;
--header: #1a1a20;
--accent: #4f46e5;
--accent-muted: #4f46e533;
--text: #e2e8f0;
--text-muted: #94a3b8;
--border: #ffffff14;
}
body {
background: var(--bg);
color: var(--text);
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
display: flex;
flex-direction: column;
height: 100vh;
}
header {
background: var(--header);
padding: 0.75rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
z-index: 10;
}
.logo {
font-weight: 800;
font-size: 1.25rem;
letter-spacing: -0.025em;
background: linear-gradient(135deg, #fff 0%, #a5b4fc 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.controls-container {
display: flex;
gap: 1.5rem;
align-items: center;
background: #ffffff05;
padding: 0.45rem 1rem;
border-radius: 12px;
border: 1px solid var(--border);
}
.control-item {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
color: var(--text-muted);
}
select {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
padding: 0.35rem 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
outline: none;
cursor: pointer;
transition: border-color 0.2s;
}
select:focus {
border-color: var(--accent);
}
input[type="checkbox"] {
appearance: none;
width: 1rem;
height: 1rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
position: relative;
transition: background 0.2s, border-color 0.2s;
}
input[type="checkbox"]:checked {
background: var(--accent);
border-color: var(--accent);
}
input[type="checkbox"]:checked::after {
content: "✓";
position: absolute;
color: white;
font-size: 0.7rem;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#chat-container {
flex: 1;
overflow-y: auto;
padding: 2rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
max-width: 900px;
margin: 0 auto;
width: 100%;
box-sizing: border-box;
}
.message {
max-width: 85%;
padding: 1rem 1.25rem;
border-radius: 16px;
line-height: 1.6;
font-size: 0.95rem;
}
.user {
align-self: flex-end;
background: var(--accent);
color: white;
border-bottom-right-radius: 4px;
box-shadow: 0 4px 12px var(--accent-muted);
}
.assistant {
align-self: flex-start;
background: var(--surface);
border: 1px solid var(--border);
border-bottom-left-radius: 4px;
}
.thinking {
font-family: "JetBrains Mono", monospace;
font-size: 0.8rem;
color: var(--text-muted);
margin-bottom: 0.75rem;
padding: 0.75rem 1rem;
border-left: 2px solid var(--accent);
background: #ffffff03;
border-radius: 4px;
white-space: pre-wrap;
opacity: 0.8;
}
#input-area {
background: var(--header);
padding: 1.5rem 2rem;
display: flex;
gap: 1rem;
border-top: 1px solid var(--border);
justify-content: center;
}
.input-wrapper {
display: flex;
gap: 0.75rem;
width: 100%;
background: #ffffff03;
padding: 0.4rem;
border-radius: 12px;
border: 1px solid var(--border);
}
input[type="text"] {
flex: 1;
background: var(--bg);
border: 1px solid var(--border);
color: white;
padding: 0.85rem 1.25rem;
border-radius: 12px;
outline: none;
font-size: 1rem;
transition: border-color 0.2s, box-shadow 0.2s;
}
input[type="text"]:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-muted);
}
button#send-btn {
background: var(--accent);
color: white;
border: none;
padding: 0 1.5rem;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
transition: transform 0.1s, opacity 0.2s;
}
button#send-btn:hover {
opacity: 0.9;
}
button#send-btn:active {
transform: scale(0.98);
}
button#send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
</head>
<body>
<header>
<div class="logo">ogenius</div>
<div class="controls-container">
<label class="control-item">
<input type="checkbox" id="thinking-toggle" checked>
<span>Thinking</span>
</label>
<div class="control-item">
<select id="model-select">
<option>Loading models...</option>
</select>
</div>
</div>
</header>
<div id="chat-container"></div>
<div id="input-area">
<div class="input-wrapper">
<input type="text" id="user-input" placeholder="Ask a genius question..." autocomplete="off">
<button id="send-btn">Send</button>
</div>
</div>
<script>
const chatContainer = document.getElementById('chat-container');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const modelSelect = document.getElementById('model-select');
const thinkingToggle = document.getElementById('thinking-toggle');
let ws = null;
let currentAssistantMsg = null;
let currentThinkingBlock = null;
async function loadModels() {
try {
const res = await fetch('/v1/models');
const result = await res.json();
const models = result.models || [];
const preferred = result.preferred;
modelSelect.innerHTML = models.map(m => `<option value="${m.id}">${m.id}</option>`).join('');
if (preferred && models.some(m => m.id === preferred)) {
modelSelect.value = preferred;
}
} catch (e) {
modelSelect.innerHTML = '<option>Error loading models</option>';
}
}
function appendMessage(role, content) {
const div = document.createElement('div');
div.className = `message ${role}`;
div.textContent = content;
chatContainer.appendChild(div);
chatContainer.scrollTop = chatContainer.scrollHeight;
return div;
}
async function connectWS() {
try {
const configRes = await fetch('/v1/config');
const config = await configRes.json();
const wsAddr = config.ws_addr.startsWith('ws')
? config.ws_addr
: `ws://${window.location.hostname}:${config.ws_addr.split(':').pop()}`;
console.log('Connecting to WebSocket:', wsAddr);
ws = new WebSocket(wsAddr);
ws.onopen = () => console.log('Connected to WebSocket');
ws.onclose = () => {
console.log('Disconnected from WebSocket, retrying...');
setTimeout(connectWS, 1000);
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.Event) {
const ev = msg.Event;
if (ev === 'ProcessStart') {
} else if (ev.Thought) {
if (thinkingToggle.checked) {
handleThought(ev.Thought);
}
} else if (ev.Content) {
handleContent(ev.Content);
} else if (ev === 'Complete') {
currentAssistantMsg = null;
currentThinkingBlock = null;
userInput.disabled = false;
sendBtn.disabled = false;
}
} else if (msg.Error) {
appendMessage('assistant', 'Error: ' + msg.Error);
}
};
} catch (e) {
console.error('Failed to connect to WebSocket:', e);
setTimeout(connectWS, 2000);
}
}
function handleThought(thought) {
if (!currentThinkingBlock) {
currentThinkingBlock = document.createElement('div');
currentThinkingBlock.className = 'thinking';
currentThinkingBlock.textContent = 'Thinking: ';
chatContainer.appendChild(currentThinkingBlock);
}
if (thought === 'Start') {
} else if (thought.Delta) {
currentThinkingBlock.textContent += thought.Delta;
} else if (thought === 'Stop') {
}
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function handleContent(content) {
if (!currentAssistantMsg) {
currentAssistantMsg = appendMessage('assistant', '');
}
currentAssistantMsg.textContent += content;
chatContainer.scrollTop = chatContainer.scrollHeight;
}
async function sendMessage() {
const text = userInput.value.trim();
if (!text || ws.readyState !== WebSocket.OPEN) return;
appendMessage('user', text);
userInput.value = '';
userInput.disabled = true;
sendBtn.disabled = true;
ws.send(JSON.stringify({
prompt: text,
model: modelSelect.value
}));
}
sendBtn.addEventListener('click', sendMessage);
userInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
loadModels();
connectWS();
userInput.focus();
</script>
</body>
</html>