XortronOS / chat.html
darkc0de's picture
Update chat.html
8862362 verified
Raw
History Blame Contribute Delete
30.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XORTRON - Criminal Computing</title>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
:root {
--glass-bg: rgba(10, 10, 12, 0.6);
--glass-border: rgba(255, 255, 255, 0.08);
--user-msg-bg: rgba(79, 70, 229, 0.3);
--user-msg-border: rgba(79, 70, 229, 0.4);
--bot-msg-bg: rgba(255, 255, 255, 0.05);
--bot-msg-border: rgba(255, 255, 255, 0.1);
--text-main: #e2e8f0;
--text-muted: #94a3b8;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Orbitron', sans-serif;
background-color: #000000;
color: var(--text-main);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
position: relative;
}
.orb {
position: absolute; border-radius: 50%; filter: blur(90px); z-index: 0; opacity: 0.35;
}
/* Orbs are 20% faster */
.orb-1 { top: -10%; left: -10%; width: 500px; height: 500px; background: #4f46e5; animation: float 9.6s infinite ease-in-out; }
.orb-2 { bottom: -15%; right: -10%; width: 600px; height: 600px; background: #400101; animation: float 12s infinite ease-in-out reverse; }
.orb-3 { top: 30%; left: 50%; width: 400px; height: 400px; background: #06b6d4; animation: float 8s infinite ease-in-out 2s; }
/* Movement distance and scale increased by 20% */
@keyframes float { 0%, 100% { transform: translateY(0) scale(1); } 50% { transform: translateY(-50px) scale(1.1); } }
.chat-container {
width: 95%; max-width: 1100px; height: 88vh;
background: var(--glass-bg);
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
border: 1px solid var(--glass-border); border-radius: 24px;
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.6);
display: flex; flex-direction: column; z-index: 10; overflow: hidden;
}
header {
padding: 20px 25px; border-bottom: 1px solid var(--glass-border);
display: flex; align-items: center; justify-content: space-between; background: rgba(0, 0, 0, 0.4);
}
.header-left { display: flex; align-items: center; gap: 12px; }
.status-dot { width: 10px; height: 10px; background-color: #10b981; border-radius: 50%; box-shadow: 0 0 10px #10b981; }
header h2 { font-size: 1.2rem; font-weight: 500; letter-spacing: 0.5px; text-transform: uppercase; }
.header-controls { display: flex; gap: 10px; }
.btn {
background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1);
color: var(--text-muted); padding: 8px 14px; border-radius: 10px;
cursor: pointer; font-size: 0.85rem; font-family: inherit; transition: all 0.2s ease;
}
.btn:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
.clear-btn:hover { background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.4); color: #fca5a5; }
.avatar-area {
display: flex; flex-direction: column; align-items: center; justify-content: center;
padding: 15px; border-bottom: 1px solid var(--glass-border); background: rgba(0, 0, 0, 0.2);
}
.avatar-core {
width: 50px; height: 50px; border-radius: 50%;
background: radial-gradient(circle, #38bdf8 0%, #0284c7 50%, #000 100%);
box-shadow: 0 0 15px #38bdf8; transition: all 0.3s ease;
}
.avatar-status { font-size: 0.75rem; color: var(--text-muted); margin-top: 8px; letter-spacing: 1px; text-transform: uppercase;}
.state-idle { animation: core-breathe 4s infinite ease-in-out; }
.state-thinking { background: radial-gradient(circle, #f59e0b 0%, #b45309 50%, #000 100%); box-shadow: 0 0 20px #f59e0b; animation: core-spin 1s infinite linear; }
.state-speaking { background: radial-gradient(circle, #06b6d4 0%, #0369a1 50%, #000 100%); box-shadow: 0 0 35px #06b6d4; animation: core-talk 0.15s infinite alternate; }
@keyframes core-breathe { 0%, 100% { transform: scale(1); opacity: 0.8; } 50% { transform: scale(1.05); opacity: 1; } }
@keyframes core-talk { 0% { transform: scale(1); } 100% { transform: scale(1.2); box-shadow: 0 0 40px #06b6d4; } }
@keyframes core-spin { 0% { transform: scale(0.9) rotate(0deg); } 50% { transform: scale(1.1) rotate(180deg); } 100% { transform: scale(0.9) rotate(360deg); } }
.chat-box { flex: 1; padding: 25px; overflow-y: auto; display: flex; flex-direction: column; gap: 20px; }
.chat-box::-webkit-scrollbar { width: 6px; }
.chat-box::-webkit-scrollbar-track { background: transparent; }
.chat-box::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 10px; }
.message-wrapper { display: flex; flex-direction: column; max-width: 80%; animation: fadeIn 0.3s ease forwards; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.message-wrapper.user { align-self: flex-end; align-items: flex-end; }
.message-wrapper.bot { align-self: flex-start; align-items: flex-start; }
.message { padding: 14px 18px; border-radius: 18px; line-height: 1.6; font-size: 0.95rem; word-wrap: break-word; }
.message-wrapper.user .message { background: var(--user-msg-bg); border: 1px solid var(--user-msg-border); border-bottom-right-radius: 4px; color: #fff; }
.message-wrapper.bot .message { background: var(--bot-msg-bg); border: 1px solid var(--bot-msg-border); border-bottom-left-radius: 4px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); }
.message p { margin-bottom: 10px; }
.message p:last-child { margin-bottom: 0; }
.message pre { background: rgba(0, 0, 0, 0.4); padding: 12px; border-radius: 8px; overflow-x: auto; margin: 10px 0; border: 1px solid rgba(255,255,255,0.05); }
.message code { font-family: 'Courier New', Courier, monospace; background: rgba(0, 0, 0, 0.3); padding: 2px 6px; border-radius: 4px; font-size: 0.85rem; color: #38bdf8; }
.message pre code { background: transparent; padding: 0; color: #e2e8f0; }
.message a { color: #38bdf8; text-decoration: none; }
/* --- 3-DOT TYING INDICATOR STYLES --- */
.typing {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 6px;
}
.typing span {
width: 8px;
height: 8px;
background-color: #38bdf8;
border-radius: 50%;
display: inline-block;
opacity: 0.4;
animation: typing-bounce 1.4s infinite ease-in-out both;
}
.typing span:nth-child(1) {
animation-delay: -0.32s;
}
.typing span:nth-child(2) {
animation-delay: -0.16s;
}
@keyframes typing-bounce {
0%, 80%, 100% {
transform: scale(0.6);
opacity: 0.4;
}
40% {
transform: scale(1.1);
opacity: 1;
box-shadow: 0 0 8px #38bdf8;
}
}
/* --- ENHANCED MARKDOWN STYLES --- */
/* Tables */
.message table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
display: block;
overflow-x: auto;
}
.message th, .message td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
border-right: 1px solid rgba(255, 255, 255, 0.05);
}
.message th:last-child, .message td:last-child {
border-right: none;
}
.message th {
background: rgba(255, 255, 255, 0.08);
color: #38bdf8;
font-weight: 600;
text-transform: uppercase;
font-size: 0.85rem;
letter-spacing: 0.5px;
border-bottom: 2px solid rgba(255, 255, 255, 0.15);
}
.message tr:last-child td {
border-bottom: none;
}
.message tbody tr {
transition: background 0.2s ease;
}
.message tbody tr:hover td {
background: rgba(56, 189, 248, 0.05);
}
/* Lists */
.message ul, .message ol {
margin: 10px 0 10px 20px;
padding-left: 10px;
}
.message li {
margin-bottom: 6px;
}
.message li::marker {
color: #38bdf8;
}
/* Blockquotes */
.message blockquote {
border-left: 4px solid #38bdf8;
padding: 10px 15px;
margin: 15px 0;
background: rgba(0, 0, 0, 0.25);
border-radius: 0 8px 8px 0;
color: var(--text-muted);
font-style: italic;
}
/* Horizontal Rules */
.message hr {
border: none;
border-top: 1px dashed rgba(255, 255, 255, 0.15);
margin: 20px 0;
}
.input-area { padding: 20px 25px; border-top: 1px solid var(--glass-border); display: flex; gap: 15px; background: rgba(0, 0, 0, 0.25); }
.input-area input { flex: 1; background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.1); padding: 16px 20px; border-radius: 14px; color: #fff; font-size: 1rem; font-family: inherit; outline: none; transition: all 0.3s ease; }
.input-area input:focus { border-color: rgba(79, 70, 229, 0.6); background: rgba(0, 0, 0, 0.5); }
.input-area button { background: linear-gradient(135deg, #4f46e5, #3b82f6); border: none; width: 55px; border-radius: 14px; color: white; cursor: pointer; display: flex; justify-content: center; align-items: center; transition: transform 0.2s, box-shadow 0.2s; box-shadow: 0 4px 15px rgba(79, 70, 229, 0.4); }
.input-area button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(79, 70, 229, 0.6); }
.input-area button svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; }
/* Custom Dialog Modal */
.modal-overlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
display: none; /* Changed to display: none/flex instead of opacity tricks */
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-overlay.active {
display: flex;
}
.modal-content {
background: rgba(15, 15, 20, 0.95);
border: 1px solid rgba(239, 68, 68, 0.3);
box-shadow: 0 0 25px rgba(239, 68, 68, 0.15);
padding: 25px;
border-radius: 16px;
max-width: 440px;
width: 90%;
text-align: center;
animation: modalScale 0.2s ease forwards;
}
@keyframes modalScale {
from { transform: scale(0.9); }
to { transform: scale(1); }
}
.modal-title {
font-size: 1.1rem;
color: #ef4444;
margin-bottom: 12px;
letter-spacing: 1px;
text-transform: uppercase;
}
.modal-text {
font-size: 0.9rem;
color: var(--text-muted);
margin-bottom: 20px;
line-height: 1.5;
}
.modal-buttons {
display: flex;
justify-content: center;
gap: 15px;
}
@media (max-width: 600px) { .chat-container { width: 100%; height: 100vh; border-radius: 0; border: none; } .message-wrapper { max-width: 90%; } }
</style>
</head>
<body>
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
<div class="chat-container">
<header>
<div class="header-left">
<div class="status-dot"></div>
<h2>XORTRON - Criminal Computing</h2>
</div>
<div class="header-controls">
<button id="clear-btn" class="btn clear-btn">Clear History</button>
</div>
</header>
<div class="avatar-area">
<div id="avatar-core" class="avatar-core state-idle"></div>
<div id="avatar-status" class="avatar-status">System Idle</div>
</div>
<div class="chat-box" id="chat-box"></div>
<div class="input-area">
<input type="text" id="message-input" placeholder="Message XORTRON..." autocomplete="off">
<button id="send-btn">
<svg viewBox="0 0 24 24"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
</button>
</div>
</div>
<!-- Custom Modal Dialog (Clear History) -->
<div id="confirm-modal" class="modal-overlay">
<div class="modal-content">
<h3 class="modal-title">Purge Logs?</h3>
<p class="modal-text">Are you sure you want to delete all local conversation history? This action cannot be undone.</p>
<div class="modal-buttons">
<button id="modal-confirm" class="btn clear-btn">Purge</button>
<button id="modal-cancel" class="btn">Cancel</button>
</div>
</div>
</div>
<!-- Custom Welcome Modal Dialog -->
<div id="welcome-modal" class="modal-overlay">
<div class="modal-content" style="border-color: rgba(79, 70, 229, 0.3); box-shadow: 0 0 25px rgba(79, 70, 229, 0.15);">
<h3 class="modal-title" style="color: #38bdf8;">System Notice</h3>
<p class="modal-text" style="text-align: left; margin-bottom: 24px;">
This space is a part of The XORTRON Criminal Computing project; an ongoing research experiment and exercise in AI safety and alignment.
<br><br>
XORTRON is provided completely free of charge and without limits, funded entirely by voluntary community donations.
<br><br>
If you find this project useful, please consider supporting its development, compute, and inference costs at:
<br>
<a href="https://ko-fi.com/xortron" target="_blank" style="color: #38bdf8; text-decoration: underline; font-weight: bold; display: block; margin-top: 8px; text-align: center;">ko-fi.com/xortron</a>
</p>
<div class="modal-buttons">
<button id="welcome-close" class="btn" style="background: linear-gradient(135deg, #4f46e5, #3b82f6); color: white; width: 100%;">Acknowledge</button>
</div>
</div>
</div>
<script>
const API_URL = "https://darkc0de-xortronapi.hf.space/v1/chat/completions";
const STORAGE_KEY = "xortron_chat_history";
const chatBox = document.getElementById('chat-box');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const clearBtn = document.getElementById('clear-btn');
const avatarCore = document.getElementById('avatar-core');
const avatarStatus = document.getElementById('avatar-status');
// Modal Elements
const confirmModal = document.getElementById('confirm-modal');
const modalConfirm = document.getElementById('modal-confirm');
const modalCancel = document.getElementById('modal-cancel');
const welcomeModal = document.getElementById('welcome-modal');
const welcomeClose = document.getElementById('welcome-close');
let conversationHistory = [];
// --- Avatar & TTS State Management ---
let currentState = 'idle';
const synth = window.speechSynthesis;
// Streaming state variables
let sentenceBuffer = "";
let inCodeBlock = false;
let activeUtterances = 0;
let isStreamDone = false;
function updateAvatarState(state, text) {
currentState = state;
avatarCore.className = `avatar-core state-${state}`;
avatarStatus.innerText = text;
}
// --- CHUNKED SPEECH QUEUE ENGINE ---
function enqueueSpeech(text) {
let cleanText = text;
// Inline code snippets
cleanText = cleanText.replace(/`[^`]+`/g, ' ');
// URLs
cleanText = cleanText.replace(/https?:\/\/[^\s]+/g, ' this link ');
// Emojis
cleanText = cleanText.replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu, '');
// Markdown links & formatting
cleanText = cleanText.replace(/\[(.*?)\]\(.*?\)/g, '$1');
cleanText = cleanText.replace(/[*_#~>|\-:]/g, '');
// Extra spaces
cleanText = cleanText.replace(/\s+/g, ' ').trim();
if (!cleanText) return;
activeUtterances++;
const utterance = new SpeechSynthesisUtterance(cleanText);
// --- SPEED UP THE VOICE ---
utterance.rate = 1.2;
// ---------------------------
const voices = synth.getVoices();
const engVoice = voices.find(v => v.lang.includes('en'));
if(engVoice) utterance.voice = engVoice;
utterance.onstart = () => { updateAvatarState('speaking', 'Transmitting Audio...'); };
utterance.onend = () => {
activeUtterances--;
checkSpeechFinished();
};
utterance.onerror = () => {
activeUtterances--;
checkSpeechFinished();
};
synth.speak(utterance);
}
function checkSpeechFinished() {
if (activeUtterances === 0 && isStreamDone) {
updateAvatarState('idle', 'System Idle');
}
}
marked.setOptions({ breaks: true, gfm: true });
function scrollToBottom() { chatBox.scrollTop = chatBox.scrollHeight; }
function appendMessage(role, text, isHTML = false) {
const wrapperDiv = document.createElement('div');
wrapperDiv.className = `message-wrapper ${role}`;
const msgDiv = document.createElement('div');
msgDiv.className = 'message';
if (isHTML && role === 'bot') { msgDiv.innerHTML = marked.parse(text); } else { msgDiv.textContent = text; }
wrapperDiv.appendChild(msgDiv);
chatBox.appendChild(wrapperDiv);
scrollToBottom();
return wrapperDiv;
}
function addTypingIndicator() {
const wrapperDiv = document.createElement('div');
wrapperDiv.className = `message-wrapper bot typing-indicator`;
const msgDiv = document.createElement('div');
msgDiv.className = 'message';
msgDiv.innerHTML = `<div class="typing"><span></span><span></span><span></span></div>`;
wrapperDiv.appendChild(msgDiv);
chatBox.appendChild(wrapperDiv);
scrollToBottom();
return wrapperDiv;
}
function saveHistory() { localStorage.setItem(STORAGE_KEY, JSON.stringify(conversationHistory)); }
function loadHistory() {
const savedData = localStorage.getItem(STORAGE_KEY);
if (savedData) {
try {
conversationHistory = JSON.parse(savedData);
if (conversationHistory.length > 0) {
conversationHistory.forEach(msg => {
const uiRole = msg.role === 'user' ? 'user' : 'bot';
appendMessage(uiRole, msg.content, uiRole === 'bot');
});
scrollToBottom();
}
} catch (error) {}
}
}
// --- Custom Dialog Control Functions ---
function showClearConfirm() {
confirmModal.classList.add('active');
}
function hideClearConfirm() {
confirmModal.classList.remove('active');
}
function executeClear() {
conversationHistory = [];
localStorage.removeItem(STORAGE_KEY);
chatBox.innerHTML = '';
hideClearConfirm();
}
async function sendMessage() {
const text = messageInput.value.trim();
if (!text) return;
synth.cancel();
updateAvatarState('thinking', 'Processing Request...');
// Reset stream states for the new message
sentenceBuffer = "";
inCodeBlock = false;
activeUtterances = 0;
isStreamDone = false;
appendMessage('user', text);
messageInput.value = '';
messageInput.disabled = true;
sendBtn.disabled = true;
conversationHistory.push({ role: "user", content: text });
saveHistory();
const loadingIndicator = addTypingIndicator();
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' },
body: JSON.stringify({
model: "darkc0de/3.6-27B-Uncensored",
messages: conversationHistory,
stream: true,
temperature: 1.0,
top_p: 0.95,
top_k: 20,
max_tokens: 32768,
presence_penalty: 1.1,
chat_template_kwargs: { "enable_thinking": false }
})
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
loadingIndicator.remove();
const wrapperDiv = document.createElement('div');
wrapperDiv.className = `message-wrapper bot`;
const msgDiv = document.createElement('div');
msgDiv.className = 'message';
wrapperDiv.appendChild(msgDiv);
chatBox.appendChild(wrapperDiv);
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let done = false;
let botReply = "";
let jsonBuffer = "";
while (!done) {
const { value, done: readerDone } = await reader.read();
done = readerDone;
if (value) {
jsonBuffer += decoder.decode(value, { stream: true });
const lines = jsonBuffer.split('\n');
jsonBuffer = lines.pop();
for (const line of lines) {
if (line.trim() === '') continue;
if (line.startsWith('data: ')) {
const dataStr = line.substring(6).trim();
if (dataStr === '[DONE]') {
done = true;
break;
}
try {
const data = JSON.parse(dataStr);
if (data.choices && data.choices[0].delta && data.choices[0].delta.content) {
const delta = data.choices[0].delta.content;
botReply += delta;
msgDiv.innerHTML = marked.parse(botReply);
scrollToBottom();
// --- STREAM TO SPEECH LOGIC ---
sentenceBuffer += delta;
// 1. Check for Code Block Toggles
let codeBlockIndex = sentenceBuffer.indexOf("```");
while (codeBlockIndex !== -1) {
if (!inCodeBlock) {
// Entering a code block: Speak what we have so far
let beforeCode = sentenceBuffer.substring(0, codeBlockIndex);
if (beforeCode.trim()) enqueueSpeech(beforeCode);
enqueueSpeech(" I have provided the code for you in the chat interface. ");
inCodeBlock = true;
} else {
// Exiting a code block
inCodeBlock = false;
}
// Discard the processed text
sentenceBuffer = sentenceBuffer.substring(codeBlockIndex + 3);
codeBlockIndex = sentenceBuffer.indexOf("```");
}
// 2. Check for sentence boundaries if NOT in a code block
if (!inCodeBlock) {
let match;
// Matches ., !, ?, or \n followed by a space
const sentenceRegex = /([.!?]+|\n)\s+/;
while ((match = sentenceBuffer.match(sentenceRegex)) !== null) {
let splitIndex = match.index + match[0].length;
let sentence = sentenceBuffer.substring(0, splitIndex);
if (sentence.trim()) {
enqueueSpeech(sentence);
}
// Remove the spoken sentence from the buffer
sentenceBuffer = sentenceBuffer.substring(splitIndex);
}
}
// ------------------------------
}
} catch (e) { }
}
}
}
}
// Stream complete
isStreamDone = true;
// Flush any remaining text in the buffer that didn't end in punctuation
if (!inCodeBlock && sentenceBuffer.trim()) {
enqueueSpeech(sentenceBuffer);
sentenceBuffer = "";
}
// Check if speech already finished (in case of very short responses)
checkSpeechFinished();
conversationHistory.push({ role: "assistant", content: botReply });
saveHistory();
} catch (error) {
loadingIndicator.remove();
appendMessage('bot', `⚠️ **Error:** Unable to connect to the AI endpoint.`, true);
isStreamDone = true;
checkSpeechFinished();
} finally {
messageInput.disabled = false;
sendBtn.disabled = false;
messageInput.focus();
scrollToBottom();
}
}
// Event Listeners for Chat actions
sendBtn.addEventListener('click', sendMessage);
// Event Listeners for Custom Confirmation Modal
clearBtn.addEventListener('click', showClearConfirm);
modalConfirm.addEventListener('click', executeClear);
modalCancel.addEventListener('click', hideClearConfirm);
confirmModal.addEventListener('click', (e) => {
if (e.target === confirmModal) hideClearConfirm();
});
// Event Listener for Welcome Modal Close
welcomeClose.addEventListener('click', () => {
welcomeModal.classList.remove('active');
});
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') { e.preventDefault(); sendMessage(); }
});
if (speechSynthesis.onvoiceschanged !== undefined) {
speechSynthesis.onvoiceschanged = () => synth.getVoices();
}
window.onload = () => {
loadHistory();
messageInput.focus();
// Always display the welcome modal upon loading the page
welcomeModal.classList.add('active');
};
</script>
</body>
</html>