darkc0de commited on
Commit
2c4609e
·
verified ·
1 Parent(s): d612b83

Upload index (6).html

Browse files
Files changed (1) hide show
  1. index (6).html +546 -0
index (6).html ADDED
@@ -0,0 +1,546 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>XORTRON - Criminal Computing</title>
7
+
8
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&display=swap" rel="stylesheet">
9
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
10
+
11
+ <style>
12
+ :root {
13
+ --glass-bg: rgba(10, 10, 12, 0.6);
14
+ --glass-border: rgba(255, 255, 255, 0.08);
15
+ --user-msg-bg: rgba(79, 70, 229, 0.3);
16
+ --user-msg-border: rgba(79, 70, 229, 0.4);
17
+ --bot-msg-bg: rgba(255, 255, 255, 0.05);
18
+ --bot-msg-border: rgba(255, 255, 255, 0.1);
19
+ --text-main: #e2e8f0;
20
+ --text-muted: #94a3b8;
21
+ }
22
+
23
+ * { box-sizing: border-box; margin: 0; padding: 0; }
24
+
25
+ body {
26
+ font-family: 'Orbitron', sans-serif;
27
+ background-color: #000000;
28
+ color: var(--text-main);
29
+ height: 100vh;
30
+ display: flex;
31
+ justify-content: center;
32
+ align-items: center;
33
+ overflow: hidden;
34
+ position: relative;
35
+ }
36
+
37
+ .orb {
38
+ position: absolute; border-radius: 50%; filter: blur(90px); z-index: 0; opacity: 0.35;
39
+ }
40
+ /* Orbs are 20% faster */
41
+ .orb-1 { top: -10%; left: -10%; width: 500px; height: 500px; background: #4f46e5; animation: float 9.6s infinite ease-in-out; }
42
+ .orb-2 { bottom: -15%; right: -10%; width: 600px; height: 600px; background: #ec4899; animation: float 12s infinite ease-in-out reverse; }
43
+ .orb-3 { top: 30%; left: 50%; width: 400px; height: 400px; background: #06b6d4; animation: float 8s infinite ease-in-out 2s; }
44
+
45
+ /* Movement distance and scale increased by 20% */
46
+ @keyframes float { 0%, 100% { transform: translateY(0) scale(1); } 50% { transform: translateY(-50px) scale(1.1); } }
47
+
48
+ .chat-container {
49
+ width: 95%; max-width: 900px; height: 88vh;
50
+ background: var(--glass-bg);
51
+ backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
52
+ border: 1px solid var(--glass-border); border-radius: 24px;
53
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.6);
54
+ display: flex; flex-direction: column; z-index: 10; overflow: hidden;
55
+ }
56
+
57
+ header {
58
+ padding: 20px 25px; border-bottom: 1px solid var(--glass-border);
59
+ display: flex; align-items: center; justify-content: space-between; background: rgba(0, 0, 0, 0.4);
60
+ }
61
+
62
+ .header-left { display: flex; align-items: center; gap: 12px; }
63
+ .status-dot { width: 10px; height: 10px; background-color: #10b981; border-radius: 50%; box-shadow: 0 0 10px #10b981; }
64
+ header h2 { font-size: 1.2rem; font-weight: 500; letter-spacing: 0.5px; text-transform: uppercase; }
65
+ .header-controls { display: flex; gap: 10px; }
66
+
67
+ .btn {
68
+ background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1);
69
+ color: var(--text-muted); padding: 8px 14px; border-radius: 10px;
70
+ cursor: pointer; font-size: 0.85rem; font-family: inherit; transition: all 0.2s ease;
71
+ }
72
+ .btn:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
73
+ .clear-btn:hover { background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.4); color: #fca5a5; }
74
+
75
+ .avatar-area {
76
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
77
+ padding: 15px; border-bottom: 1px solid var(--glass-border); background: rgba(0, 0, 0, 0.2);
78
+ }
79
+
80
+ .avatar-core {
81
+ width: 50px; height: 50px; border-radius: 50%;
82
+ background: radial-gradient(circle, #38bdf8 0%, #0284c7 50%, #000 100%);
83
+ box-shadow: 0 0 15px #38bdf8; transition: all 0.3s ease;
84
+ }
85
+
86
+ .avatar-status { font-size: 0.75rem; color: var(--text-muted); margin-top: 8px; letter-spacing: 1px; text-transform: uppercase;}
87
+
88
+ .state-idle { animation: core-breathe 4s infinite ease-in-out; }
89
+ .state-thinking { background: radial-gradient(circle, #f59e0b 0%, #b45309 50%, #000 100%); box-shadow: 0 0 20px #f59e0b; animation: core-spin 1s infinite linear; }
90
+ .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; }
91
+
92
+ @keyframes core-breathe { 0%, 100% { transform: scale(1); opacity: 0.8; } 50% { transform: scale(1.05); opacity: 1; } }
93
+ @keyframes core-talk { 0% { transform: scale(1); } 100% { transform: scale(1.2); box-shadow: 0 0 40px #06b6d4; } }
94
+ @keyframes core-spin { 0% { transform: scale(0.9) rotate(0deg); } 50% { transform: scale(1.1) rotate(180deg); } 100% { transform: scale(0.9) rotate(360deg); } }
95
+
96
+ .chat-box { flex: 1; padding: 25px; overflow-y: auto; display: flex; flex-direction: column; gap: 20px; }
97
+ .chat-box::-webkit-scrollbar { width: 6px; }
98
+ .chat-box::-webkit-scrollbar-track { background: transparent; }
99
+ .chat-box::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 10px; }
100
+
101
+ .message-wrapper { display: flex; flex-direction: column; max-width: 80%; animation: fadeIn 0.3s ease forwards; }
102
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
103
+ .message-wrapper.user { align-self: flex-end; align-items: flex-end; }
104
+ .message-wrapper.bot { align-self: flex-start; align-items: flex-start; }
105
+
106
+ .message { padding: 14px 18px; border-radius: 18px; line-height: 1.6; font-size: 0.95rem; word-wrap: break-word; }
107
+ .message-wrapper.user .message { background: var(--user-msg-bg); border: 1px solid var(--user-msg-border); border-bottom-right-radius: 4px; color: #fff; }
108
+ .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); }
109
+
110
+ .message p { margin-bottom: 10px; }
111
+ .message p:last-child { margin-bottom: 0; }
112
+ .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); }
113
+ .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; }
114
+ .message pre code { background: transparent; padding: 0; color: #e2e8f0; }
115
+ .message a { color: #38bdf8; text-decoration: none; }
116
+
117
+ /* --- ENHANCED MARKDOWN STYLES --- */
118
+
119
+ /* Tables */
120
+ .message table {
121
+ width: 100%;
122
+ border-collapse: collapse;
123
+ margin: 15px 0;
124
+ background: rgba(0, 0, 0, 0.4);
125
+ border: 1px solid rgba(255, 255, 255, 0.1);
126
+ border-radius: 8px;
127
+ display: block;
128
+ overflow-x: auto; /* Adds a scrollbar if the table is too wide */
129
+ }
130
+
131
+ .message th, .message td {
132
+ padding: 12px 15px;
133
+ text-align: left;
134
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
135
+ border-right: 1px solid rgba(255, 255, 255, 0.05);
136
+ }
137
+
138
+ .message th:last-child, .message td:last-child {
139
+ border-right: none;
140
+ }
141
+
142
+ .message th {
143
+ background: rgba(255, 255, 255, 0.08);
144
+ color: #38bdf8;
145
+ font-weight: 600;
146
+ text-transform: uppercase;
147
+ font-size: 0.85rem;
148
+ letter-spacing: 0.5px;
149
+ border-bottom: 2px solid rgba(255, 255, 255, 0.15);
150
+ }
151
+
152
+ .message tr:last-child td {
153
+ border-bottom: none;
154
+ }
155
+
156
+ .message tbody tr {
157
+ transition: background 0.2s ease;
158
+ }
159
+
160
+ .message tbody tr:hover td {
161
+ background: rgba(56, 189, 248, 0.05); /* Slight cyan tint on hover */
162
+ }
163
+
164
+ /* Lists */
165
+ .message ul, .message ol {
166
+ margin: 10px 0 10px 20px;
167
+ padding-left: 10px;
168
+ }
169
+
170
+ .message li {
171
+ margin-bottom: 6px;
172
+ }
173
+
174
+ .message li::marker {
175
+ color: #38bdf8;
176
+ }
177
+
178
+ /* Blockquotes */
179
+ .message blockquote {
180
+ border-left: 4px solid #38bdf8;
181
+ padding: 10px 15px;
182
+ margin: 15px 0;
183
+ background: rgba(0, 0, 0, 0.25);
184
+ border-radius: 0 8px 8px 0;
185
+ color: var(--text-muted);
186
+ font-style: italic;
187
+ }
188
+
189
+ /* Horizontal Rules */
190
+ .message hr {
191
+ border: none;
192
+ border-top: 1px dashed rgba(255, 255, 255, 0.15);
193
+ margin: 20px 0;
194
+ }
195
+
196
+ .input-area { padding: 20px 25px; border-top: 1px solid var(--glass-border); display: flex; gap: 15px; background: rgba(0, 0, 0, 0.25); }
197
+ .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; }
198
+ .input-area input:focus { border-color: rgba(79, 70, 229, 0.6); background: rgba(0, 0, 0, 0.5); }
199
+ .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); }
200
+ .input-area button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(79, 70, 229, 0.6); }
201
+ .input-area button svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; }
202
+
203
+ .typing { display: flex; gap: 5px; padding: 5px 0; }
204
+ .typing span { width: 6px; height: 6px; background: var(--text-muted); border-radius: 50%; animation: bounce 1.4s infinite ease-in-out both; }
205
+ .typing span:nth-child(1) { animation-delay: -0.32s; }
206
+ .typing span:nth-child(2) { animation-delay: -0.16s; }
207
+ @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }
208
+
209
+ @media (max-width: 600px) { .chat-container { width: 100%; height: 100vh; border-radius: 0; border: none; } .message-wrapper { max-width: 90%; } }
210
+ </style>
211
+ </head>
212
+ <body>
213
+
214
+ <div class="orb orb-1"></div>
215
+ <div class="orb orb-2"></div>
216
+ <div class="orb orb-3"></div>
217
+
218
+ <div class="chat-container">
219
+ <header>
220
+ <div class="header-left">
221
+ <div class="status-dot"></div>
222
+ <h2>XORTRON</h2>
223
+ </div>
224
+ <div class="header-controls">
225
+ <button id="clear-btn" class="btn clear-btn">Clear History</button>
226
+ </div>
227
+ </header>
228
+
229
+ <div class="avatar-area">
230
+ <div id="avatar-core" class="avatar-core state-idle"></div>
231
+ <div id="avatar-status" class="avatar-status">System Idle</div>
232
+ </div>
233
+
234
+ <div class="chat-box" id="chat-box"></div>
235
+
236
+ <div class="input-area">
237
+ <input type="text" id="message-input" placeholder="Message XORTRON..." autocomplete="off">
238
+ <button id="send-btn">
239
+ <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>
240
+ </button>
241
+ </div>
242
+ </div>
243
+
244
+ <script>
245
+ const API_URL = "https://darkc0de-xortron7.hf.space/v1/chat/completions";
246
+ const STORAGE_KEY = "xortron_chat_history";
247
+
248
+ const chatBox = document.getElementById('chat-box');
249
+ const messageInput = document.getElementById('message-input');
250
+ const sendBtn = document.getElementById('send-btn');
251
+ const clearBtn = document.getElementById('clear-btn');
252
+ const avatarCore = document.getElementById('avatar-core');
253
+ const avatarStatus = document.getElementById('avatar-status');
254
+
255
+ let conversationHistory = [];
256
+
257
+ // --- Avatar & TTS State Management ---
258
+ let currentState = 'idle';
259
+ const synth = window.speechSynthesis;
260
+
261
+ // Streaming state variables
262
+ let sentenceBuffer = "";
263
+ let inCodeBlock = false;
264
+ let activeUtterances = 0;
265
+ let isStreamDone = false;
266
+
267
+ function updateAvatarState(state, text) {
268
+ currentState = state;
269
+ avatarCore.className = `avatar-core state-${state}`;
270
+ avatarStatus.innerText = text;
271
+ }
272
+
273
+ // --- CHUNKED SPEECH QUEUE ENGINE ---
274
+ function enqueueSpeech(text) {
275
+ let cleanText = text;
276
+
277
+ // Inline code snippets
278
+ cleanText = cleanText.replace(/`[^`]+`/g, ' ');
279
+ // URLs
280
+ cleanText = cleanText.replace(/https?:\/\/[^\s]+/g, ' this link ');
281
+ // Emojis
282
+ cleanText = cleanText.replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu, '');
283
+ // Markdown links & formatting
284
+ cleanText = cleanText.replace(/\[(.*?)\]\(.*?\)/g, '$1');
285
+ cleanText = cleanText.replace(/[*_#~>|\-:]/g, '');
286
+ // Extra spaces
287
+ cleanText = cleanText.replace(/\s+/g, ' ').trim();
288
+
289
+ if (!cleanText) return;
290
+
291
+ activeUtterances++;
292
+ const utterance = new SpeechSynthesisUtterance(cleanText);
293
+
294
+ // --- NEW: SPEED UP THE VOICE ---
295
+ utterance.rate = 1.3; // Change this value to adjust speed. Normal is 1.0. Max is usually 2.0 or 10 depending on the browser.
296
+ // -------------------------------
297
+
298
+ const voices = synth.getVoices();
299
+ const engVoice = voices.find(v => v.lang.includes('en'));
300
+ if(engVoice) utterance.voice = engVoice;
301
+
302
+ utterance.onstart = () => { updateAvatarState('speaking', 'Transmitting Audio...'); };
303
+
304
+ utterance.onend = () => {
305
+ activeUtterances--;
306
+ checkSpeechFinished();
307
+ };
308
+
309
+ utterance.onerror = () => {
310
+ activeUtterances--;
311
+ checkSpeechFinished();
312
+ };
313
+
314
+ synth.speak(utterance);
315
+ }
316
+
317
+ function checkSpeechFinished() {
318
+ if (activeUtterances === 0 && isStreamDone) {
319
+ updateAvatarState('idle', 'System Idle');
320
+ }
321
+ }
322
+
323
+ marked.setOptions({ breaks: true, gfm: true });
324
+ function scrollToBottom() { chatBox.scrollTop = chatBox.scrollHeight; }
325
+
326
+ function appendMessage(role, text, isHTML = false) {
327
+ const wrapperDiv = document.createElement('div');
328
+ wrapperDiv.className = `message-wrapper ${role}`;
329
+ const msgDiv = document.createElement('div');
330
+ msgDiv.className = 'message';
331
+ if (isHTML && role === 'bot') { msgDiv.innerHTML = marked.parse(text); } else { msgDiv.textContent = text; }
332
+ wrapperDiv.appendChild(msgDiv);
333
+ chatBox.appendChild(wrapperDiv);
334
+ scrollToBottom();
335
+ return wrapperDiv;
336
+ }
337
+
338
+ function addTypingIndicator() {
339
+ const wrapperDiv = document.createElement('div');
340
+ wrapperDiv.className = `message-wrapper bot typing-indicator`;
341
+ const msgDiv = document.createElement('div');
342
+ msgDiv.className = 'message';
343
+ msgDiv.innerHTML = `<div class="typing"><span></span><span></span><span></span></div>`;
344
+ wrapperDiv.appendChild(msgDiv);
345
+ chatBox.appendChild(wrapperDiv);
346
+ scrollToBottom();
347
+ return wrapperDiv;
348
+ }
349
+
350
+ function saveHistory() { localStorage.setItem(STORAGE_KEY, JSON.stringify(conversationHistory)); }
351
+
352
+ function loadHistory() {
353
+ const savedData = localStorage.getItem(STORAGE_KEY);
354
+ if (savedData) {
355
+ try {
356
+ conversationHistory = JSON.parse(savedData);
357
+ if (conversationHistory.length > 0) {
358
+ conversationHistory.forEach(msg => {
359
+ const uiRole = msg.role === 'user' ? 'user' : 'bot';
360
+ appendMessage(uiRole, msg.content, uiRole === 'bot');
361
+ });
362
+ scrollToBottom();
363
+ }
364
+ } catch (error) {}
365
+ }
366
+ }
367
+
368
+ function clearHistory() {
369
+ if (confirm("Are you sure you want to clear the chat history?")) {
370
+ conversationHistory = [];
371
+ localStorage.removeItem(STORAGE_KEY);
372
+ chatBox.innerHTML = '';
373
+ }
374
+ }
375
+
376
+ async function sendMessage() {
377
+ const text = messageInput.value.trim();
378
+ if (!text) return;
379
+
380
+ synth.cancel();
381
+ updateAvatarState('thinking', 'Processing Request...');
382
+
383
+ // Reset stream states for the new message
384
+ sentenceBuffer = "";
385
+ inCodeBlock = false;
386
+ activeUtterances = 0;
387
+ isStreamDone = false;
388
+
389
+ appendMessage('user', text);
390
+ messageInput.value = '';
391
+ messageInput.disabled = true;
392
+ sendBtn.disabled = true;
393
+
394
+ conversationHistory.push({ role: "user", content: text });
395
+ saveHistory();
396
+
397
+ const loadingIndicator = addTypingIndicator();
398
+
399
+ try {
400
+ const response = await fetch(API_URL, {
401
+ method: 'POST',
402
+ headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' },
403
+ body: JSON.stringify({
404
+ model: "darkc0de/3.6-27B-v2",
405
+ messages: conversationHistory,
406
+ stream: true,
407
+ temperature: 0.7,
408
+ top_p: 0.8,
409
+ top_k: 20,
410
+ max_tokens: 32768,
411
+ presence_penalty: 1.5,
412
+ chat_template_kwargs: { "enable_thinking": false }
413
+ })
414
+ });
415
+
416
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
417
+
418
+ loadingIndicator.remove();
419
+
420
+ const wrapperDiv = document.createElement('div');
421
+ wrapperDiv.className = `message-wrapper bot`;
422
+ const msgDiv = document.createElement('div');
423
+ msgDiv.className = 'message';
424
+ wrapperDiv.appendChild(msgDiv);
425
+ chatBox.appendChild(wrapperDiv);
426
+
427
+ const reader = response.body.getReader();
428
+ const decoder = new TextDecoder("utf-8");
429
+ let done = false;
430
+ let botReply = "";
431
+ let jsonBuffer = "";
432
+
433
+ while (!done) {
434
+ const { value, done: readerDone } = await reader.read();
435
+ done = readerDone;
436
+
437
+ if (value) {
438
+ jsonBuffer += decoder.decode(value, { stream: true });
439
+ const lines = jsonBuffer.split('\n');
440
+ jsonBuffer = lines.pop();
441
+
442
+ for (const line of lines) {
443
+ if (line.trim() === '') continue;
444
+ if (line.startsWith('data: ')) {
445
+ const dataStr = line.substring(6).trim();
446
+ if (dataStr === '[DONE]') {
447
+ done = true;
448
+ break;
449
+ }
450
+ try {
451
+ const data = JSON.parse(dataStr);
452
+ if (data.choices && data.choices[0].delta && data.choices[0].delta.content) {
453
+ const delta = data.choices[0].delta.content;
454
+ botReply += delta;
455
+ msgDiv.innerHTML = marked.parse(botReply);
456
+ scrollToBottom();
457
+
458
+ // --- STREAM TO SPEECH LOGIC ---
459
+ sentenceBuffer += delta;
460
+
461
+ // 1. Check for Code Block Toggles
462
+ let codeBlockIndex = sentenceBuffer.indexOf("```");
463
+ while (codeBlockIndex !== -1) {
464
+ if (!inCodeBlock) {
465
+ // Entering a code block: Speak what we have so far
466
+ let beforeCode = sentenceBuffer.substring(0, codeBlockIndex);
467
+ if (beforeCode.trim()) enqueueSpeech(beforeCode);
468
+
469
+ enqueueSpeech(" I have provided the code for you in the chat interface. ");
470
+ inCodeBlock = true;
471
+ } else {
472
+ // Exiting a code block
473
+ inCodeBlock = false;
474
+ }
475
+ // Discard the processed text
476
+ sentenceBuffer = sentenceBuffer.substring(codeBlockIndex + 3);
477
+ codeBlockIndex = sentenceBuffer.indexOf("```");
478
+ }
479
+
480
+ // 2. Check for sentence boundaries if NOT in a code block
481
+ if (!inCodeBlock) {
482
+ let match;
483
+ // Matches ., !, ?, or \n followed by a space
484
+ const sentenceRegex = /([.!?]+|\n)\s+/;
485
+ while ((match = sentenceBuffer.match(sentenceRegex)) !== null) {
486
+ let splitIndex = match.index + match[0].length;
487
+ let sentence = sentenceBuffer.substring(0, splitIndex);
488
+
489
+ if (sentence.trim()) {
490
+ enqueueSpeech(sentence);
491
+ }
492
+ // Remove the spoken sentence from the buffer
493
+ sentenceBuffer = sentenceBuffer.substring(splitIndex);
494
+ }
495
+ }
496
+ // ------------------------------
497
+ }
498
+ } catch (e) { }
499
+ }
500
+ }
501
+ }
502
+ }
503
+
504
+ // Stream complete
505
+ isStreamDone = true;
506
+
507
+ // Flush any remaining text in the buffer that didn't end in punctuation
508
+ if (!inCodeBlock && sentenceBuffer.trim()) {
509
+ enqueueSpeech(sentenceBuffer);
510
+ sentenceBuffer = "";
511
+ }
512
+
513
+ // Check if speech already finished (in case of very short responses)
514
+ checkSpeechFinished();
515
+
516
+ conversationHistory.push({ role: "assistant", content: botReply });
517
+ saveHistory();
518
+
519
+ } catch (error) {
520
+ loadingIndicator.remove();
521
+ appendMessage('bot', `⚠️ **Error:** Unable to connect to the AI endpoint.`, true);
522
+ isStreamDone = true;
523
+ checkSpeechFinished();
524
+ } finally {
525
+ messageInput.disabled = false;
526
+ sendBtn.disabled = false;
527
+ messageInput.focus();
528
+ scrollToBottom();
529
+ }
530
+ }
531
+
532
+ sendBtn.addEventListener('click', sendMessage);
533
+ clearBtn.addEventListener('click', clearHistory);
534
+
535
+ messageInput.addEventListener('keypress', (e) => {
536
+ if (e.key === 'Enter') { e.preventDefault(); sendMessage(); }
537
+ });
538
+
539
+ if (speechSynthesis.onvoiceschanged !== undefined) {
540
+ speechSynthesis.onvoiceschanged = () => synth.getVoices();
541
+ }
542
+
543
+ window.onload = () => { loadHistory(); messageInput.focus(); };
544
+ </script>
545
+ </body>
546
+ </html>