scottlily6 commited on
Commit
3ea30ed
·
verified ·
1 Parent(s): 3396e78

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +704 -19
index.html CHANGED
@@ -1,19 +1,704 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>AI Chat + Speech-to-Text</title>
7
+ <!-- Import FontAwesome for Icons -->
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+
10
+ <style>
11
+ /* ==============================
12
+ CSS VARIABLES & RESET
13
+ ============================== */
14
+ :root {
15
+ --bg-color: #f7f7f8;
16
+ --sidebar-bg: #202123;
17
+ --sidebar-text: #ececf1;
18
+ --main-bg: #ffffff;
19
+ --border-color: #e5e7eb;
20
+ --primary-color: #10a37f;
21
+ --primary-hover: #0d8a6c;
22
+ --text-primary: #343541;
23
+ --text-secondary: #6e6e80;
24
+ --user-msg-bg: #ffffff;
25
+ --ai-msg-bg: #f7f7f8;
26
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
27
+ --font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
28
+ }
29
+
30
+ * {
31
+ box-sizing: border-box;
32
+ outline: none;
33
+ }
34
+
35
+ html, body {
36
+ height: 100%;
37
+ margin: 0;
38
+ padding: 0;
39
+ font-family: var(--font-family);
40
+ background-color: var(--bg-color);
41
+ color: var(--text-primary);
42
+ }
43
+
44
+ /* ==============================
45
+ LAYOUT
46
+ ============================== */
47
+ .app-container {
48
+ display: flex;
49
+ height: 100vh;
50
+ width: 100vw;
51
+ overflow: hidden;
52
+ }
53
+
54
+ /* ==============================
55
+ SIDEBAR
56
+ ============================== */
57
+ .sidebar {
58
+ width: 260px;
59
+ background-color: var(--sidebar-bg);
60
+ color: var(--sidebar-text);
61
+ display: flex;
62
+ flex-direction: column;
63
+ padding: 10px;
64
+ transition: transform 0.3s ease;
65
+ z-index: 100;
66
+ }
67
+
68
+ .sidebar-header {
69
+ padding: 10px;
70
+ font-weight: 600;
71
+ font-size: 0.9rem;
72
+ border-bottom: 1px solid rgba(255,255,255,0.1);
73
+ margin-bottom: 10px;
74
+ }
75
+
76
+ .login-btn {
77
+ background: rgba(255,255,255,0.1);
78
+ border: 1px solid rgba(255,255,255,0.2);
79
+ color: white;
80
+ padding: 10px;
81
+ border-radius: 6px;
82
+ cursor: pointer;
83
+ width: 100%;
84
+ margin-bottom: 20px;
85
+ transition: background 0.2s;
86
+ }
87
+
88
+ .login-btn:hover {
89
+ background: rgba(255,255,255,0.2);
90
+ }
91
+
92
+ .settings-group {
93
+ padding: 0 5px;
94
+ margin-bottom: 15px;
95
+ }
96
+
97
+ .settings-group label {
98
+ display: block;
99
+ font-size: 0.75rem;
100
+ margin-bottom: 5px;
101
+ color: #8e8ea0;
102
+ }
103
+
104
+ .settings-input, .settings-textarea {
105
+ width: 100%;
106
+ background: rgba(0,0,0,0.2);
107
+ border: 1px solid rgba(255,255,255,0.1);
108
+ color: white;
109
+ padding: 8px;
110
+ border-radius: 4px;
111
+ font-size: 0.85rem;
112
+ resize: none;
113
+ }
114
+
115
+ .settings-slider-container {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 10px;
119
+ }
120
+
121
+ .settings-slider {
122
+ flex: 1;
123
+ accent-color: var(--primary-color);
124
+ }
125
+
126
+ .slider-value {
127
+ font-size: 0.75rem;
128
+ width: 30px;
129
+ text-align: right;
130
+ }
131
+
132
+ /* ==============================
133
+ MAIN CONTENT
134
+ ============================== */
135
+ .main-content {
136
+ flex: 1;
137
+ display: flex;
138
+ flex-direction: column;
139
+ background-color: var(--main-bg);
140
+ position: relative;
141
+ }
142
+
143
+ /* Header */
144
+ .header {
145
+ padding: 10px 20px;
146
+ text-align: center;
147
+ font-weight: 600;
148
+ color: #202123;
149
+ background: #ffffff;
150
+ border-bottom: 1px solid var(--border-color);
151
+ display: flex;
152
+ justify-content: space-between;
153
+ align-items: center;
154
+ }
155
+
156
+ .header-title {
157
+ font-size: 1rem;
158
+ }
159
+
160
+ .brand-link {
161
+ font-size: 0.75rem;
162
+ color: var(--text-secondary);
163
+ text-decoration: none;
164
+ transition: color 0.2s;
165
+ }
166
+
167
+ .brand-link:hover {
168
+ color: var(--primary-color);
169
+ }
170
+
171
+ /* Mobile Toggle */
172
+ .menu-toggle {
173
+ display: none;
174
+ background: none;
175
+ border: none;
176
+ font-size: 1.2rem;
177
+ cursor: pointer;
178
+ color: #555;
179
+ margin-right: 10px;
180
+ }
181
+
182
+ /* Chat Area */
183
+ .chat-container {
184
+ flex: 1;
185
+ overflow-y: auto;
186
+ padding: 20px;
187
+ display: flex;
188
+ flex-direction: column;
189
+ gap: 20px;
190
+ scroll-behavior: smooth;
191
+ }
192
+
193
+ .message {
194
+ display: flex;
195
+ gap: 15px;
196
+ max-width: 800px;
197
+ margin: 0 auto;
198
+ width: 100%;
199
+ line-height: 1.6;
200
+ }
201
+
202
+ .message.user {
203
+ justify-content: flex-end;
204
+ }
205
+
206
+ .avatar {
207
+ width: 30px;
208
+ height: 30px;
209
+ border-radius: 4px;
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ flex-shrink: 0;
214
+ font-size: 0.8rem;
215
+ }
216
+
217
+ .avatar.ai {
218
+ background: var(--primary-color);
219
+ color: white;
220
+ }
221
+
222
+ .avatar.user {
223
+ background: #555;
224
+ color: white;
225
+ order: 2; /* Avatar on right for user */
226
+ }
227
+
228
+ .message-content {
229
+ padding: 0 10px;
230
+ font-size: 1rem;
231
+ color: var(--text-primary);
232
+ white-space: pre-wrap;
233
+ }
234
+
235
+ .message.user .message-content {
236
+ background: #f0f0f0;
237
+ padding: 10px 15px;
238
+ border-radius: 12px;
239
+ border-top-right-radius: 2px;
240
+ }
241
+
242
+ .message.ai .message-content {
243
+ padding-top: 5px;
244
+ }
245
+
246
+ /* Typing indicator */
247
+ .typing-indicator span {
248
+ display: inline-block;
249
+ width: 6px;
250
+ height: 6px;
251
+ background-color: #ccc;
252
+ border-radius: 50%;
253
+ animation: typing 1.4s infinite ease-in-out both;
254
+ margin-right: 3px;
255
+ }
256
+
257
+ .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
258
+ .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
259
+
260
+ @keyframes typing {
261
+ 0%, 80%, 100% { transform: scale(0); }
262
+ 40% { transform: scale(1); }
263
+ }
264
+
265
+ /* Input Area */
266
+ .input-bar {
267
+ padding: 24px;
268
+ background: linear-gradient(180deg, rgba(255,255,255,0) 0%, #ffffff 20%);
269
+ display: flex;
270
+ justify-content: center;
271
+ }
272
+
273
+ .input-wrapper {
274
+ width: 100%;
275
+ max-width: 800px;
276
+ position: relative;
277
+ display: flex;
278
+ align-items: flex-end;
279
+ background: white;
280
+ border: 1px solid var(--border-color);
281
+ border-radius: 12px;
282
+ box-shadow: var(--shadow-sm);
283
+ padding: 10px;
284
+ transition: border-color 0.2s, box-shadow 0.2s;
285
+ }
286
+
287
+ .input-wrapper:focus-within {
288
+ border-color: rgba(0,0,0,0.2);
289
+ box-shadow: 0 2px 6px rgba(0,0,0,0.05);
290
+ }
291
+
292
+ textarea {
293
+ flex: 1;
294
+ border: none;
295
+ resize: none;
296
+ max-height: 200px;
297
+ min-height: 24px;
298
+ padding: 0 10px;
299
+ font-family: inherit;
300
+ font-size: 1rem;
301
+ line-height: 1.5;
302
+ background: transparent;
303
+ }
304
+
305
+ .icon-btn {
306
+ background: none;
307
+ border: none;
308
+ cursor: pointer;
309
+ color: #8e8ea0;
310
+ padding: 6px;
311
+ border-radius: 6px;
312
+ transition: background 0.2s, color 0.2s;
313
+ display: flex;
314
+ align-items: center;
315
+ justify-content: center;
316
+ }
317
+
318
+ .icon-btn:hover {
319
+ background: #f0f0f0;
320
+ color: #333;
321
+ }
322
+
323
+ .icon-btn.recording {
324
+ color: #ef4444;
325
+ background: #fee2e2;
326
+ animation: pulse 1.5s infinite;
327
+ }
328
+
329
+ @keyframes pulse {
330
+ 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
331
+ 70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
332
+ 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
333
+ }
334
+
335
+ .send-btn {
336
+ background: var(--primary-color);
337
+ color: white;
338
+ border-radius: 8px;
339
+ padding: 6px 12px;
340
+ }
341
+
342
+ .send-btn:hover {
343
+ background: var(--primary-hover);
344
+ }
345
+
346
+ .send-btn:disabled {
347
+ background: #ccc;
348
+ cursor: not-allowed;
349
+ }
350
+
351
+ /* Scrollbar Styling */
352
+ ::-webkit-scrollbar {
353
+ width: 8px;
354
+ }
355
+ ::-webkit-scrollbar-track {
356
+ background: transparent;
357
+ }
358
+ ::-webkit-scrollbar-thumb {
359
+ background: #d1d5db;
360
+ border-radius: 4px;
361
+ }
362
+ ::-webkit-scrollbar-thumb:hover {
363
+ background: #9ca3af;
364
+ }
365
+
366
+ /* ==============================
367
+ RESPONSIVE DESIGN
368
+ ============================== */
369
+ @media (max-width: 768px) {
370
+ .sidebar {
371
+ position: absolute;
372
+ height: 100%;
373
+ transform: translateX(-100%);
374
+ box-shadow: 2px 0 10px rgba(0,0,0,0.2);
375
+ }
376
+
377
+ .sidebar.active {
378
+ transform: translateX(0);
379
+ }
380
+
381
+ .menu-toggle {
382
+ display: block;
383
+ }
384
+
385
+ .input-bar {
386
+ padding: 10px;
387
+ }
388
+ }
389
+ </style>
390
+ </head>
391
+ <body>
392
+
393
+ <div class="app-container">
394
+ <!-- SIDEBAR -->
395
+ <aside class="sidebar" id="sidebar">
396
+ <div class="sidebar-header">
397
+ <i class="fa-solid fa-sliders"></i> Settings
398
+ </div>
399
+
400
+ <button class="login-btn">
401
+ <i class="fa-brands fa-huggingface"></i> Login to HuggingFace
402
+ </button>
403
+
404
+ <div class="settings-group">
405
+ <label>System Prompt</label>
406
+ <textarea class="settings-textarea" id="system-prompt" rows="4">You are a helpful assistant.</textarea>
407
+ </div>
408
+
409
+ <div class="settings-group">
410
+ <label>Max Tokens</label>
411
+ <div class="settings-slider-container">
412
+ <input type="range" class="settings-slider" id="max-tokens" min="1" max="2048" value="2012">
413
+ <span class="slider-value" id="max-tokens-val">2012</span>
414
+ </div>
415
+ </div>
416
+
417
+ <div class="settings-group">
418
+ <label>Temperature</label>
419
+ <div class="settings-slider-container">
420
+ <input type="range" class="settings-slider" id="temperature" min="0.1" max="4.0" step="0.1" value="0.7">
421
+ <span class="slider-value" id="temperature-val">0.7</span>
422
+ </div>
423
+ </div>
424
+
425
+ <div class="settings-group">
426
+ <label>Top P</label>
427
+ <div class="settings-slider-container">
428
+ <input type="range" class="settings-slider" id="top-p" min="0.1" max="1.0" step="0.05" value="0.95">
429
+ <span class="slider-value" id="top-p-val">0.95</span>
430
+ </div>
431
+ </div>
432
+ </aside>
433
+
434
+ <!-- MAIN CONTENT -->
435
+ <main class="main-content">
436
+ <!-- Header -->
437
+ <header class="header">
438
+ <div style="display: flex; align-items: center;">
439
+ <button class="menu-toggle" id="menu-toggle">
440
+ <i class="fa-solid fa-bars"></i>
441
+ </button>
442
+ <div class="header-title">AI Chat + Speech-to-Text</div>
443
+ </div>
444
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link">
445
+ Built with anycoder <i class="fa-solid fa-arrow-up-right-from-square" style="font-size: 0.7em;"></i>
446
+ </a>
447
+ </header>
448
+
449
+ <!-- Chat Area -->
450
+ <div class="chat-container" id="chat-container">
451
+ <!-- Messages will be injected here via JS -->
452
+ <div class="message ai">
453
+ <div class="avatar ai"><i class="fa-solid fa-robot"></i></div>
454
+ <div class="message-content">Hello! I am ready to chat. You can type or use the microphone to speak.</div>
455
+ </div>
456
+ </div>
457
+
458
+ <!-- Input Area -->
459
+ <div class="input-bar">
460
+ <div class="input-wrapper">
461
+ <textarea id="message-input" placeholder="Message..." rows="1"></textarea>
462
+
463
+ <button class="icon-btn" id="mic-btn" title="Speak">
464
+ <i class="fa-solid fa-microphone"></i>
465
+ </button>
466
+
467
+ <button class="icon-btn send-btn" id="send-btn" title="Send">
468
+ <i class="fa-solid fa-paper-plane"></i>
469
+ </button>
470
+ </div>
471
+ </div>
472
+ </main>
473
+ </div>
474
+
475
+ <script>
476
+ // ==============================
477
+ // STATE & DOM ELEMENTS
478
+ // ==============================
479
+ const state = {
480
+ history: [],
481
+ isRecording: false
482
+ };
483
+
484
+ const elements = {
485
+ chatContainer: document.getElementById('chat-container'),
486
+ messageInput: document.getElementById('message-input'),
487
+ sendBtn: document.getElementById('send-btn'),
488
+ micBtn: document.getElementById('mic-btn'),
489
+ menuToggle: document.getElementById('menu-toggle'),
490
+ sidebar: document.getElementById('sidebar'),
491
+ systemPrompt: document.getElementById('system-prompt'),
492
+ sliders: {
493
+ maxTokens: document.getElementById('max-tokens'),
494
+ temperature: document.getElementById('temperature'),
495
+ topP: document.getElementById('top-p')
496
+ },
497
+ sliderVals: {
498
+ maxTokens: document.getElementById('max-tokens-val'),
499
+ temperature: document.getElementById('temperature-val'),
500
+ topP: document.getElementById('top-p-val')
501
+ }
502
+ };
503
+
504
+ // ==============================
505
+ // EVENT LISTENERS
506
+ // ==============================
507
+
508
+ // Auto-resize textarea
509
+ elements.messageInput.addEventListener('input', function() {
510
+ this.style.height = 'auto';
511
+ this.style.height = (this.scrollHeight) + 'px';
512
+ if(this.value === '') this.style.height = 'auto';
513
+ });
514
+
515
+ // Send on Enter (Shift+Enter for new line)
516
+ elements.messageInput.addEventListener('keydown', (e) => {
517
+ if (e.key === 'Enter' && !e.shiftKey) {
518
+ e.preventDefault();
519
+ handleSend();
520
+ }
521
+ });
522
+
523
+ // Send Button Click
524
+ elements.sendBtn.addEventListener('click', handleSend);
525
+
526
+ // Mobile Menu Toggle
527
+ elements.menuToggle.addEventListener('click', () => {
528
+ elements.sidebar.classList.toggle('active');
529
+ });
530
+
531
+ // Close sidebar when clicking outside on mobile
532
+ document.addEventListener('click', (e) => {
533
+ if (window.innerWidth <= 768) {
534
+ if (!elements.sidebar.contains(e.target) && !elements.menuToggle.contains(e.target)) {
535
+ elements.sidebar.classList.remove('active');
536
+ }
537
+ }
538
+ });
539
+
540
+ // Update Slider Values Display
541
+ Object.keys(elements.sliders).forEach(key => {
542
+ elements.sliders[key].addEventListener('input', (e) => {
543
+ elements.sliderVals[key].textContent = e.target.value;
544
+ });
545
+ });
546
+
547
+ // ==============================
548
+ // CHAT LOGIC
549
+ // ==============================
550
+
551
+ function handleSend() {
552
+ const text = elements.messageInput.value.trim();
553
+ if (!text) return;
554
+
555
+ // Add User Message
556
+ addMessageToUI('user', text);
557
+ state.history.push({ role: 'user', content: text });
558
+
559
+ // Clear Input
560
+ elements.messageInput.value = '';
561
+ elements.messageInput.style.height = 'auto';
562
+
563
+ // Simulate AI Response (Streaming)
564
+ simulateAIResponse(text);
565
+ }
566
+
567
+ function addMessageToUI(role, content, isTyping = false) {
568
+ const msgDiv = document.createElement('div');
569
+ msgDiv.className = `message ${role}`;
570
+
571
+ const avatarDiv = document.createElement('div');
572
+ avatarDiv.className = `avatar ${role}`;
573
+
574
+ if (role === 'ai') {
575
+ avatarDiv.innerHTML = '<i class="fa-solid fa-robot"></i>';
576
+ } else {
577
+ avatarDiv.innerHTML = '<i class="fa-solid fa-user"></i>';
578
+ }
579
+
580
+ const contentDiv = document.createElement('div');
581
+ contentDiv.className = 'message-content';
582
+
583
+ if (isTyping) {
584
+ contentDiv.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
585
+ } else {
586
+ contentDiv.textContent = content;
587
+ }
588
+
589
+ msgDiv.appendChild(avatarDiv);
590
+ msgDiv.appendChild(contentDiv);
591
+
592
+ elements.chatContainer.appendChild(msgDiv);
593
+ scrollToBottom();
594
+ return contentDiv; // Return content div for streaming updates
595
+ }
596
+
597
+ function scrollToBottom() {
598
+ elements.chatContainer.scrollTop = elements.chatContainer.scrollHeight;
599
+ }
600
+
601
+ // ==============================
602
+ // SIMULATED AI STREAMING
603
+ // ==============================
604
+ function simulateAIResponse(userMessage) {
605
+ // 1. Add placeholder with typing indicator
606
+ const contentDiv = addMessageToUI('ai', '', true);
607
+
608
+ // 2. Simulate network delay
609
+ setTimeout(() => {
610
+ // Remove typing indicator
611
+ contentDiv.innerHTML = '';
612
+
613
+ // Generate a dummy response based on settings
614
+ const temp = parseFloat(elements.sliders.temperature.value);
615
+ const sysPrompt = elements.systemPrompt.value;
616
+
617
+ let responseText = "";
618
+
619
+ // Simple mock logic to make the tool feel alive
620
+ if (userMessage.toLowerCase().includes('hello')) {
621
+ responseText = "Hello there! I'm running in the browser using vanilla JS. How can I help you today?";
622
+ } else if (userMessage.toLowerCase().includes('code')) {
623
+ responseText = "Sure, here is a Python example:\n\n```python\ndef greet():\n print('Hello World')\n```";
624
+ } else {
625
+ responseText = `(Simulated Response based on Temp: ${temp})\nI received your message: "${userMessage}".\n\nSystem Prompt is currently set to: "${sysPrompt}".\n\nSince this is a client-side demo, I'm simulating the streaming text effect that the HuggingFace InferenceClient would provide.`;
626
+ }
627
+
628
+ // 3. Stream the text character by character
629
+ let index = 0;
630
+ const speed = 20; // ms per char
631
+
632
+ function typeChar() {
633
+ if (index < responseText.length) {
634
+ // Handle simple newlines
635
+ const char = responseText.charAt(index);
636
+ contentDiv.textContent += char;
637
+ index++;
638
+ scrollToBottom();
639
+ setTimeout(typeChar, speed);
640
+ } else {
641
+ // Done streaming
642
+ state.history.push({ role: 'assistant', content: responseText });
643
+ }
644
+ }
645
+
646
+ typeChar();
647
+
648
+ }, 800);
649
+ }
650
+
651
+ // ==============================
652
+ // SPEECH TO TEXT (Web Speech API)
653
+ // ==============================
654
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
655
+
656
+ if (SpeechRecognition) {
657
+ const recognition = new SpeechRecognition();
658
+ recognition.continuous = false;
659
+ recognition.interimResults = false;
660
+ recognition.lang = 'en-US';
661
+
662
+ elements.micBtn.addEventListener('click', () => {
663
+ if (state.isRecording) {
664
+ recognition.stop();
665
+ } else {
666
+ recognition.start();
667
+ }
668
+ });
669
+
670
+ recognition.onstart = () => {
671
+ state.isRecording = true;
672
+ elements.micBtn.classList.add('recording');
673
+ elements.messageInput.placeholder = "Listening...";
674
+ };
675
+
676
+ recognition.onend = () => {
677
+ state.isRecording = false;
678
+ elements.micBtn.classList.remove('recording');
679
+ elements.messageInput.placeholder = "Message...";
680
+ };
681
+
682
+ recognition.onresult = (event) => {
683
+ const transcript = event.results[0][0].transcript;
684
+ elements.messageInput.value = transcript;
685
+ elements.messageInput.style.height = 'auto'; // Reset height logic
686
+ elements.messageInput.style.height = (elements.messageInput.scrollHeight) + 'px';
687
+ elements.messageInput.focus();
688
+ };
689
+
690
+ recognition.onerror = (event) => {
691
+ console.error("Speech recognition error", event.error);
692
+ state.isRecording = false;
693
+ elements.micBtn.classList.remove('recording');
694
+ alert("Error accessing microphone: " + event.error);
695
+ };
696
+ } else {
697
+ // Fallback for browsers without support
698
+ elements.micBtn.style.display = 'none';
699
+ console.warn("Web Speech API not supported in this browser.");
700
+ }
701
+
702
+ </script>
703
+ </body>
704
+ </html>