Download chat.html from Mabeloooo/XortronOS: direct link, hf CLI and curl.
- Browser
- Download file 30.1 kB
-
https://huggingface.co/spaces/Mabeloooo/XortronOS/resolve/main/chat.html
- Command line
-
hf download hf://spaces/Mabeloooo/XortronOS/chat.html
-
curl -L -o chat.html https://huggingface.co/spaces/Mabeloooo/XortronOS/resolve/main/chat.html
30.1 kB
| <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> |