hermescures1's picture
Upload folder using huggingface_hub
32112fa verified
Raw
History Blame Contribute Delete
12 kB
"""Chat UI HTML for Singularity LLM — served at / endpoint."""
CHAT_UI_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Singularity LLM</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0a0f; color: #e0e0e0; font-family: 'Segoe UI', system-ui, sans-serif; height: 100vh; display: flex; flex-direction: column; }
#header { background: #12121a; padding: 12px 20px; border-bottom: 1px solid #222; display: flex; justify-content: space-between; align-items: center; }
#header h1 { font-size: 18px; color: #7c7cff; }
#header .stats-btn { background: #1a1a2e; color: #888; border: 1px solid #333; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; }
#header .stats-btn:hover { background: #222238; color: #aaa; }
#chat { flex: 1; overflow-y: auto; padding: 20px; max-width: 900px; margin: 0 auto; width: 100%; }
.msg { margin-bottom: 16px; max-width: 80%; }
.msg.user { margin-left: auto; }
.msg .bubble { padding: 12px 16px; border-radius: 12px; line-height: 1.5; white-space: pre-wrap; word-wrap: break-word; }
.msg.user .bubble { background: #1a3a5c; color: #cde; }
.msg.assistant .bubble { background: #1c1c2e; color: #d0d0e0; }
.msg .meta { font-size: 11px; color: #555; margin-top: 4px; }
#input-area { background: #12121a; padding: 16px 20px; border-top: 1px solid #222; }
#input-form { display: flex; gap: 10px; max-width: 900px; margin: 0 auto; }
#msg-input { flex: 1; background: #1a1a2e; color: #e0e0e0; border: 1px solid #333; padding: 12px 16px; border-radius: 8px; font-size: 14px; outline: none; }
#msg-input:focus { border-color: #444; }
#send-btn { background: #4a4aff; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-size: 14px; }
#send-btn:hover { background: #5a5aff; }
#send-btn:disabled { background: #333; cursor: not-allowed; }
#voice-btn { background: #1a1a2e; color: #888; border: 1px solid #333; padding: 12px 16px; border-radius: 8px; cursor: pointer; font-size: 14px; }
#voice-btn:hover { background: #222238; }
#voice-btn.active { background: #ff4a4a; color: white; border-color: #ff4a4a; }
#stats-panel { display: none; position: fixed; top: 0; right: 0; width: 400px; height: 100vh; background: #12121a; border-left: 1px solid #222; padding: 20px; overflow-y: auto; z-index: 100; }
#stats-panel h2 { font-size: 16px; color: #7c7cff; margin-bottom: 16px; }
#stats-panel pre { font-size: 12px; color: #aaa; white-space: pre-wrap; line-height: 1.4; }
#stats-panel .close { position: absolute; top: 16px; right: 16px; cursor: pointer; color: #666; font-size: 20px; }
.typing { color: #666; font-style: italic; }
</style>
</head>
<body>
<div id="header">
<h1>⚡ Singularity LLM</h1>
<button class="stats-btn" onclick="toggleStats()">Stats</button>
</div>
<div id="chat"></div>
<div id="input-area">
<form id="input-form">
<button type="button" id="voice-btn" onclick="toggleVoice()">🎤</button>
<input type="text" id="msg-input" placeholder="Type a message..." autocomplete="off" autofocus>
<button type="submit" id="send-btn">Send</button>
</form>
</div>
<div id="stats-panel">
<span class="close" onclick="toggleStats()">&times;</span>
<h2>Model Statistics</h2>
<pre id="stats-content">Loading...</pre>
</div>
<script>
const chat = document.getElementById('chat');
const form = document.getElementById('input-form');
const input = document.getElementById('msg-input');
const sendBtn = document.getElementById('send-btn');
const voiceBtn = document.getElementById('voice-btn');
function addMsg(role, text, meta) {
const div = document.createElement('div');
div.className = 'msg ' + role;
div.innerHTML = '<div class="bubble"></div>' + (meta ? '<div class="meta">' + meta + '</div>' : '');
div.querySelector('.bubble').textContent = text;
chat.appendChild(div);
chat.scrollTop = chat.scrollHeight;
return div;
}
async function sendMessage(text) {
if (!text.trim()) return;
addMsg('user', text);
input.value = '';
sendBtn.disabled = true;
const typing = addMsg('assistant', '...');
typing.querySelector('.bubble').className = 'bubble typing';
typing.querySelector('.bubble').textContent = 'Thinking...';
try {
const resp = await fetch('/v1/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: text, channel: 'web' }),
});
const data = await resp.json();
typing.querySelector('.bubble').className = 'bubble';
typing.querySelector('.bubble').textContent = data.response || '(no response)';
if (data.elapsed_s) {
typing.querySelector('.meta').textContent = data.elapsed_s + 's';
}
} catch (e) {
typing.querySelector('.bubble').textContent = 'Error: ' + e.message;
}
sendBtn.disabled = false;
input.focus();
}
form.addEventListener('submit', (e) => {
e.preventDefault();
sendMessage(input.value);
});
// Voice support via Web Speech API
let recognition = null;
let isListening = false;
function toggleVoice() {
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
alert('Voice input not supported in this browser');
return;
}
if (isListening) {
recognition.stop();
return;
}
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SR();
recognition.continuous = false;
recognition.interimResults = false;
recognition.lang = 'en-US';
recognition.onstart = () => {
isListening = true;
voiceBtn.classList.add('active');
};
recognition.onresult = (e) => {
const text = e.results[0][0].transcript;
sendMessage(text);
};
recognition.onerror = (e) => {
console.error('Voice error:', e.error);
};
recognition.onend = () => {
isListening = false;
voiceBtn.classList.remove('active');
};
recognition.start();
}
// Stats panel
function toggleStats() {
const panel = document.getElementById('stats-panel');
if (panel.style.display === 'block') {
panel.style.display = 'none';
} else {
panel.style.display = 'block';
loadStats();
}
}
async function loadStats() {
try {
const resp = await fetch('/v1/stats');
const data = await resp.json();
document.getElementById('stats-content').textContent = JSON.stringify(data, null, 2);
} catch (e) {
document.getElementById('stats-content').textContent = 'Error: ' + e.message;
}
}
</script>
</body>
</html>"""
JARVIS_UI_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jarvis — Singularity Voice Assistant</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0a0f; color: #e0e0e0; font-family: 'Segoe UI', system-ui, sans-serif; height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; }
h1 { color: #7c7cff; font-size: 24px; margin-bottom: 8px; }
#status { color: #666; font-size: 14px; margin-bottom: 30px; }
#waveform { width: 200px; height: 60px; margin-bottom: 30px; display: flex; align-items: center; justify-content: center; gap: 3px; }
.bar { width: 4px; background: #4a4aff; border-radius: 2px; transition: height 0.1s; height: 4px; }
#talk-btn { width: 120px; height: 120px; border-radius: 50%; border: 3px solid #333; background: #1a1a2e; color: #666; font-size: 40px; cursor: pointer; transition: all 0.2s; }
#talk-btn:hover { border-color: #4a4aff; color: #aaa; }
#talk-btn.listening { border-color: #ff4a4a; background: #2a1a1a; color: #ff4a4a; animation: pulse 1s infinite; }
#talk-btn.speaking { border-color: #4aff4a; background: #1a2a1a; color: #4aff4a; }
@keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.05); } }
#conversation { width: 90%; max-width: 600px; margin-top: 30px; max-height: 200px; overflow-y: auto; }
.msg { margin-bottom: 8px; font-size: 14px; }
.msg.user { color: #6a9; text-align: right; }
.msg.jarvis { color: #7c7cff; }
</style>
</head>
<body>
<h1>⚡ Jarvis</h1>
<div id="status">Click to talk</div>
<div id="waveform"></div>
<button id="talk-btn" onclick="toggleTalk()">🎤</button>
<div id="conversation"></div>
<script>
const statusEl = document.getElementById('status');
const talkBtn = document.getElementById('talk-btn');
const convEl = document.getElementById('conversation');
const waveEl = document.getElementById('waveform');
// Create waveform bars
for (let i = 0; i < 40; i++) {
const bar = document.createElement('div');
bar.className = 'bar';
waveEl.appendChild(bar);
}
const bars = document.querySelectorAll('.bar');
let recognition = null;
let synth = window.speechSynthesis;
let isListening = false;
let isSpeaking = false;
function addMsg(role, text) {
const div = document.createElement('div');
div.className = 'msg ' + role;
div.textContent = (role === 'user' ? 'You: ' : 'Jarvis: ') + text;
convEl.appendChild(div);
convEl.scrollTop = convEl.scrollHeight;
}
function setStatus(text) { statusEl.textContent = text; }
function animateWave(active) {
bars.forEach((bar, i) => {
if (active) {
bar.style.height = (Math.random() * 50 + 5) + 'px';
} else {
bar.style.height = '4px';
}
});
}
let waveInterval = null;
function startWaveAnim() {
if (waveInterval) return;
waveInterval = setInterval(() => animateWave(true), 80);
}
function stopWaveAnim() {
if (waveInterval) { clearInterval(waveInterval); waveInterval = null; }
animateWave(false);
}
function speak(text) {
if (!synth) return;
synth.cancel();
const utter = new SpeechSynthesisUtterance(text);
utter.rate = 1.1;
utter.onstart = () => { isSpeaking = true; talkBtn.classList.add('speaking'); setStatus('Speaking...'); };
utter.onend = () => { isSpeaking = false; talkBtn.classList.remove('speaking'); setStatus('Click to talk'); };
synth.speak(utter);
}
async function sendToJarvis(text) {
setStatus('Thinking...');
try {
const resp = await fetch('/v1/voice/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: text }),
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let fullText = '';
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('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;
fullText += data;
if (data.trim().endsWith('.') || data.trim().endsWith('!') || data.trim().endsWith('?')) {
speak(data.trim());
}
}
}
}
addMsg('jarvis', fullText);
setStatus('Click to talk');
} catch (e) {
setStatus('Error: ' + e.message);
}
}
function toggleTalk() {
if (isSpeaking) { synth.cancel(); return; }
if (isListening) { recognition.stop(); return; }
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
alert('Voice not supported. Use Chrome/Edge.');
return;
}
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SR();
recognition.continuous = false;
recognition.interimResults = false;
recognition.lang = 'en-US';
recognition.onstart = () => {
isListening = true;
talkBtn.classList.add('listening');
setStatus('Listening...');
startWaveAnim();
};
recognition.onresult = (e) => {
const text = e.results[0][0].transcript;
addMsg('user', text);
sendToJarvis(text);
};
recognition.onerror = (e) => { setStatus('Error: ' + e.error); };
recognition.onend = () => {
isListening = false;
talkBtn.classList.remove('listening');
stopWaveAnim();
if (!isSpeaking) setStatus('Click to talk');
};
recognition.start();
}
</script>
</body>
</html>"""