Xlnk commited on
Commit
40376b1
·
verified ·
1 Parent(s): e767f32

Delete uploads

Browse files
Files changed (1) hide show
  1. uploads/1/index.html +0 -1592
uploads/1/index.html DELETED
@@ -1,1592 +0,0 @@
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>Xlnk AI Chat</title>
7
- <style>
8
- /* Green Animated Profile Styles */
9
- .message.assistant .avatar {
10
- background: linear-gradient(135deg, #10a37f 0%, #19c37d 100%);
11
- border: 2px solid #10a37f;
12
- box-shadow:
13
- 0 0 10px #10a37f,
14
- 0 0 20px #10a37f,
15
- inset 0 0 10px rgba(16, 163, 127, 0.3);
16
- animation: avatar-glow 3s infinite alternate, subtle-float 4s infinite ease-in-out;
17
- overflow: hidden;
18
- position: relative;
19
- }
20
-
21
- .message.assistant .avatar::before {
22
- content: '';
23
- position: absolute;
24
- top: -50%;
25
- left: -50%;
26
- width: 200%;
27
- height: 200%;
28
- background: linear-gradient(
29
- 45deg,
30
- transparent 30%,
31
- rgba(16, 163, 127, 0.3) 50%,
32
- transparent 70%
33
- );
34
- animation: shine 3s infinite linear;
35
- }
36
-
37
- @keyframes avatar-glow {
38
- 0% {
39
- box-shadow:
40
- 0 0 10px #10a37f,
41
- 0 0 20px #10a37f,
42
- inset 0 0 10px rgba(16, 163, 127, 0.3);
43
- }
44
- 100% {
45
- box-shadow:
46
- 0 0 15px #10a37f,
47
- 0 0 30px #10a37f,
48
- 0 0 40px #10a37f,
49
- inset 0 0 15px rgba(16, 163, 127, 0.5);
50
- }
51
- }
52
-
53
- @keyframes subtle-float {
54
- 0%, 100% { transform: translateY(0px); }
55
- 50% { transform: translateY(-5px); }
56
- }
57
-
58
- @keyframes shine {
59
- 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
60
- 100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
61
- }
62
-
63
- .message.assistant .avatar img {
64
- width: 100%;
65
- height: 100%;
66
- object-fit: cover;
67
- border-radius: 4px;
68
- position: relative;
69
- z-index: 1;
70
- animation: image-pulse 2s infinite;
71
- }
72
-
73
- @keyframes image-pulse {
74
- 0%, 100% { opacity: 0.95; }
75
- 50% { opacity: 1; }
76
- }
77
-
78
- /* When AI is typing/thinking, enhance the animation */
79
- .message.assistant.thinking .avatar {
80
- animation: avatar-glow 1.5s infinite alternate, subtle-float 3s infinite ease-in-out;
81
- }
82
-
83
- .message.assistant.thinking .avatar::before {
84
- animation: shine 1.5s infinite linear;
85
- }
86
-
87
- /* Typing Cursor Styles - Centered Image */
88
- .typing-cursor {
89
- display: inline-flex;
90
- align-items: center;
91
- justify-content: center;
92
- width: 20px;
93
- height: 20px;
94
- border-radius: 50%;
95
- animation: cursor-blink 1s infinite;
96
- position: relative;
97
- overflow: hidden;
98
- border: 2px solid #10a37f;
99
- box-shadow:
100
- 0 0 6px #10a37f,
101
- 0 0 12px #10a37f,
102
- inset 0 0 6px rgba(16, 163, 127, 0.5);
103
- vertical-align: middle;
104
- margin-left: 4px;
105
- margin-bottom: -2px; /* Fine adjustment for vertical centering */
106
- }
107
-
108
- .typing-cursor img {
109
- width: 100%;
110
- height: 100%;
111
- object-fit: cover;
112
- border-radius: 50%;
113
- display: block;
114
- position: relative;
115
- z-index: 2;
116
- }
117
-
118
- @keyframes cursor-blink {
119
- 0%, 50% { opacity: 1; }
120
- 51%, 100% { opacity: 0.3; }
121
- }
122
-
123
- .typing-cursor::before {
124
- content: '';
125
- position: absolute;
126
- top: -50%;
127
- left: -50%;
128
- width: 200%;
129
- height: 200%;
130
- background: linear-gradient(
131
- 45deg,
132
- transparent 30%,
133
- rgba(16, 163, 127, 0.4) 50%,
134
- transparent 70%
135
- );
136
- animation: cursor-shine 2s infinite linear;
137
- z-index: 1;
138
- }
139
-
140
- @keyframes cursor-shine {
141
- 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
142
- 100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
143
- }
144
-
145
- /* Enhanced cursor animation when typing */
146
- .message.assistant.thinking .typing-cursor {
147
- animation: cursor-blink 0.8s infinite, cursor-pulse 1.5s infinite;
148
- }
149
-
150
- @keyframes cursor-pulse {
151
- 0%, 100% { transform: scale(1); }
152
- 50% { transform: scale(1.1); }
153
- }
154
-
155
- /* Ensure proper text alignment */
156
- .message-content {
157
- position: relative;
158
- display: inline-block;
159
- line-height: 1.6;
160
- }
161
-
162
- .typing-indicator {
163
- display: inline-block;
164
- width: 8px;
165
- height: 8px;
166
- border-radius: 50%;
167
- background: #9ca3af;
168
- animation: pulse 1.4s infinite;
169
- vertical-align: middle;
170
- }
171
-
172
- @keyframes pulse {
173
- 0%, 100% {
174
- opacity: 0.3;
175
- }
176
- 50% {
177
- opacity: 1;
178
- }
179
- }
180
-
181
- /* Existing styles remain unchanged */
182
- * {
183
- margin: 0;
184
- padding: 0;
185
- box-sizing: border-box;
186
- }
187
-
188
- body {
189
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', sans-serif;
190
- background: #1a1a1a;
191
- height: 100vh;
192
- display: flex;
193
- flex-direction: column;
194
- }
195
-
196
- .header {
197
- background: #2a2a2a;
198
- border-bottom: 1px solid #3a3a3a;
199
- padding: 12px 20px;
200
- display: flex;
201
- align-items: center;
202
- justify-content: space-between;
203
- box-shadow: 0 1px 3px rgba(0,0,0,0.3);
204
- }
205
-
206
- .header h1 {
207
- font-size: 18px;
208
- font-weight: 600;
209
- color: #e5e5e5;
210
- }
211
-
212
- .model-selector {
213
- display: flex;
214
- align-items: center;
215
- gap: 8px;
216
- }
217
-
218
- .model-selector label {
219
- font-size: 13px;
220
- color: #9ca3af;
221
- font-weight: 500;
222
- }
223
-
224
- .model-selector select {
225
- padding: 6px 12px;
226
- border: 1px solid #4a4a4a;
227
- border-radius: 6px;
228
- font-size: 13px;
229
- background: #3a3a3a;
230
- color: #e5e5e5;
231
- cursor: pointer;
232
- outline: none;
233
- transition: border-color 0.2s;
234
- }
235
-
236
- .model-selector select:hover {
237
- border-color: #6a6a6a;
238
- }
239
-
240
- .model-selector select:focus {
241
- border-color: #10a37f;
242
- }
243
-
244
- .sidebar {
245
- position: fixed;
246
- left: 0;
247
- top: 0;
248
- width: 260px;
249
- height: 100vh;
250
- background: #2a2a2a;
251
- border-right: 1px solid #3a3a3a;
252
- display: flex;
253
- flex-direction: column;
254
- transition: transform 0.3s;
255
- z-index: 10;
256
- }
257
-
258
- .sidebar-header {
259
- padding: 16px;
260
- border-bottom: 1px solid #3a3a3a;
261
- display: flex;
262
- align-items: center;
263
- gap: 8px;
264
- }
265
-
266
- .new-chat-btn {
267
- flex: 1;
268
- padding: 10px;
269
- background: #3a3a3a;
270
- color: #e5e5e5;
271
- border: 1px solid #4a4a4a;
272
- border-radius: 6px;
273
- font-size: 13px;
274
- cursor: pointer;
275
- transition: background 0.2s;
276
- }
277
-
278
- .new-chat-btn:hover {
279
- background: #4a4a4a;
280
- }
281
-
282
- .chat-history {
283
- flex: 1;
284
- overflow-y: auto;
285
- padding: 8px;
286
- }
287
-
288
- .sidebar-footer {
289
- padding: 12px;
290
- border-top: 1px solid #3a3a3a;
291
- }
292
-
293
- .clear-all-btn {
294
- width: 100%;
295
- padding: 10px;
296
- background: transparent;
297
- color: #ef4444;
298
- border: 1px solid #ef4444;
299
- border-radius: 6px;
300
- font-size: 13px;
301
- cursor: pointer;
302
- transition: all 0.2s;
303
- }
304
-
305
- .clear-all-btn:hover {
306
- background: #ef4444;
307
- color: white;
308
- }
309
-
310
- .history-item {
311
- padding: 10px 12px;
312
- margin-bottom: 4px;
313
- border-radius: 6px;
314
- cursor: pointer;
315
- font-size: 13px;
316
- color: #9ca3af;
317
- background: transparent;
318
- transition: background 0.2s;
319
- display: flex;
320
- align-items: center;
321
- justify-content: space-between;
322
- gap: 8px;
323
- }
324
-
325
- .history-item:hover {
326
- background: #3a3a3a;
327
- }
328
-
329
- .history-item.active {
330
- background: #3a3a3a;
331
- color: #e5e5e5;
332
- }
333
-
334
- .history-item-title {
335
- flex: 1;
336
- white-space: nowrap;
337
- overflow: hidden;
338
- text-overflow: ellipsis;
339
- }
340
-
341
- .delete-chat-btn {
342
- padding: 4px 8px;
343
- background: transparent;
344
- color: #ef4444;
345
- border: none;
346
- border-radius: 4px;
347
- font-size: 12px;
348
- cursor: pointer;
349
- opacity: 0;
350
- transition: all 0.2s;
351
- }
352
-
353
- .history-item:hover .delete-chat-btn {
354
- opacity: 1;
355
- }
356
-
357
- .delete-chat-btn:hover {
358
- background: #ef4444;
359
- color: white;
360
- }
361
-
362
- .close-sidebar-btn {
363
- background: #3a3a3a;
364
- color: #e5e5e5;
365
- border: 1px solid #4a4a4a;
366
- padding: 10px 14px;
367
- border-radius: 6px;
368
- font-size: 18px;
369
- font-weight: bold;
370
- cursor: pointer;
371
- transition: all 0.2s;
372
- line-height: 1;
373
- display: flex;
374
- align-items: center;
375
- justify-content: center;
376
- }
377
-
378
- .close-sidebar-btn:hover {
379
- background: #ef4444;
380
- border-color: #ef4444;
381
- }
382
-
383
- .main-content {
384
- margin-left: 260px;
385
- display: flex;
386
- flex-direction: column;
387
- height: 100vh;
388
- }
389
-
390
- .toggle-sidebar {
391
- display: none;
392
- position: fixed;
393
- top: 16px;
394
- left: 16px;
395
- background: #3a3a3a;
396
- border: 1px solid #4a4a4a;
397
- color: #e5e5e5;
398
- padding: 8px 12px;
399
- border-radius: 6px;
400
- cursor: pointer;
401
- z-index: 5;
402
- }
403
-
404
- @media (max-width: 768px) {
405
- .sidebar {
406
- transform: translateX(-100%);
407
- width: 100%;
408
- max-width: 300px;
409
- }
410
-
411
- .sidebar.open {
412
- transform: translateX(0);
413
- }
414
-
415
- .main-content {
416
- margin-left: 0;
417
- }
418
-
419
- .toggle-sidebar {
420
- display: block;
421
- }
422
-
423
- .header {
424
- padding-left: 60px;
425
- }
426
-
427
- .header h1 {
428
- font-size: 16px;
429
- }
430
-
431
- .model-selector label {
432
- display: none;
433
- }
434
-
435
- .search-controls {
436
- flex-direction: column;
437
- align-items: flex-start;
438
- }
439
-
440
- .search-mode-badge {
441
- font-size: 10px;
442
- }
443
- }
444
-
445
- .sidebar-overlay {
446
- display: none;
447
- position: fixed;
448
- top: 0;
449
- left: 0;
450
- right: 0;
451
- bottom: 0;
452
- background: rgba(0,0,0,0.5);
453
- z-index: 9;
454
- }
455
-
456
- .sidebar-overlay.active {
457
- display: block;
458
- }
459
-
460
- .chat-container {
461
- flex: 1;
462
- overflow-y: auto;
463
- padding: 20px;
464
- padding-bottom: 160px;
465
- display: flex;
466
- flex-direction: column;
467
- gap: 16px;
468
- }
469
-
470
- .message {
471
- display: flex;
472
- gap: 12px;
473
- max-width: 800px;
474
- width: 100%;
475
- animation: fadeIn 0.3s ease-in;
476
- }
477
-
478
- @keyframes fadeIn {
479
- from {
480
- opacity: 0;
481
- transform: translateY(10px);
482
- }
483
- to {
484
- opacity: 1;
485
- transform: translateY(0);
486
- }
487
- }
488
-
489
- .message.user {
490
- align-self: flex-end;
491
- flex-direction: row-reverse;
492
- }
493
-
494
- .message.assistant {
495
- align-self: flex-start;
496
- }
497
-
498
- .avatar {
499
- width: 32px;
500
- height: 32px;
501
- border-radius: 50%;
502
- display: flex;
503
- align-items: center;
504
- justify-content: center;
505
- font-weight: 600;
506
- font-size: 14px;
507
- flex-shrink: 0;
508
- overflow: hidden;
509
- }
510
-
511
- .message.user .avatar {
512
- background: #19c37d;
513
- color: white;
514
- border: 2px solid #10a37f;
515
- box-shadow: 0 0 8px #10a37f;
516
- }
517
-
518
- .message-content {
519
- background: #2a2a2a;
520
- padding: 12px 16px;
521
- border-radius: 12px;
522
- line-height: 1.6;
523
- color: #e5e5e5;
524
- box-shadow: 0 1px 2px rgba(0,0,0,0.3);
525
- word-wrap: break-word;
526
- }
527
-
528
- .message.user .message-content {
529
- background: #4a4a4a;
530
- color: white;
531
- }
532
-
533
- .message.assistant .message-content {
534
- background: #2a2a2a;
535
- }
536
-
537
- .message-content pre {
538
- background: #1a1a1a;
539
- padding: 12px;
540
- border-radius: 6px;
541
- overflow-x: auto;
542
- margin: 8px 0;
543
- border: 1px solid #3a3a3a;
544
- }
545
-
546
- .message-content code {
547
- background: #1a1a1a;
548
- padding: 2px 6px;
549
- border-radius: 4px;
550
- font-family: 'Courier New', monospace;
551
- font-size: 13px;
552
- color: #10a37f;
553
- }
554
-
555
- .message-content pre code {
556
- background: transparent;
557
- padding: 0;
558
- color: #e5e5e5;
559
- }
560
-
561
- .message-content a {
562
- color: #10a37f;
563
- text-decoration: none;
564
- border-bottom: 1px solid transparent;
565
- transition: border-color 0.2s;
566
- }
567
-
568
- .message-content a:hover {
569
- border-bottom-color: #10a37f;
570
- }
571
-
572
- .search-results-preview {
573
- background: #1a1a1a;
574
- border: 1px solid #3a3a3a;
575
- border-radius: 8px;
576
- padding: 12px;
577
- margin: 8px 0;
578
- font-size: 13px;
579
- }
580
-
581
- .search-results-preview .result-item {
582
- padding: 8px 0;
583
- border-bottom: 1px solid #2a2a2a;
584
- }
585
-
586
- .search-results-preview .result-item:last-child {
587
- border-bottom: none;
588
- }
589
-
590
- .search-results-preview .result-title {
591
- color: #10a37f;
592
- font-weight: 500;
593
- margin-bottom: 4px;
594
- }
595
-
596
- .search-results-preview .result-snippet {
597
- color: #9ca3af;
598
- font-size: 12px;
599
- line-height: 1.4;
600
- }
601
-
602
- .search-results-preview .result-url {
603
- color: #6a6a6a;
604
- font-size: 11px;
605
- margin-top: 4px;
606
- }
607
-
608
- .search-results-preview .result-url a {
609
- color: #6a6a6a;
610
- }
611
-
612
- .search-results-preview .result-url a:hover {
613
- color: #10a37f;
614
- }
615
-
616
- .input-container {
617
- position: fixed;
618
- bottom: 20px;
619
- left: 280px;
620
- right: 20px;
621
- background: #2a2a2a;
622
- border: 1px solid #3a3a3a;
623
- border-radius: 16px;
624
- padding: 16px 20px;
625
- box-shadow: 0 4px 20px rgba(0,0,0,0.4);
626
- z-index: 5;
627
- }
628
-
629
- @media (max-width: 768px) {
630
- .input-container {
631
- left: 20px;
632
- }
633
- }
634
-
635
- .input-wrapper {
636
- max-width: 800px;
637
- margin: 0 auto;
638
- display: flex;
639
- gap: 8px;
640
- flex-wrap: wrap;
641
- }
642
-
643
- .search-controls {
644
- width: 100%;
645
- display: flex;
646
- align-items: center;
647
- justify-content: space-between;
648
- gap: 12px;
649
- margin-bottom: 12px;
650
- }
651
-
652
- .web-search-toggle {
653
- display: flex;
654
- align-items: center;
655
- gap: 10px;
656
- padding: 8px 14px;
657
- background: #3a3a3a;
658
- border: 1px solid #4a4a4a;
659
- border-radius: 24px;
660
- cursor: pointer;
661
- transition: all 0.2s;
662
- }
663
-
664
- .web-search-toggle:hover {
665
- border-color: #5a5a5a;
666
- }
667
-
668
- .web-search-toggle.active {
669
- background: #10a37f20;
670
- border-color: #10a37f;
671
- }
672
-
673
- .web-search-toggle .toggle-switch {
674
- width: 36px;
675
- height: 20px;
676
- background: #4a4a4a;
677
- border-radius: 10px;
678
- position: relative;
679
- transition: all 0.2s;
680
- }
681
-
682
- .web-search-toggle.active .toggle-switch {
683
- background: #10a37f;
684
- }
685
-
686
- .web-search-toggle .toggle-switch::after {
687
- content: '';
688
- position: absolute;
689
- width: 16px;
690
- height: 16px;
691
- background: white;
692
- border-radius: 50%;
693
- top: 2px;
694
- left: 2px;
695
- transition: all 0.2s;
696
- }
697
-
698
- .web-search-toggle.active .toggle-switch::after {
699
- left: 18px;
700
- }
701
-
702
- .web-search-toggle .toggle-label {
703
- font-size: 13px;
704
- color: #9ca3af;
705
- font-weight: 500;
706
- }
707
-
708
- .web-search-toggle.active .toggle-label {
709
- color: #10a37f;
710
- }
711
-
712
- .web-search-toggle .search-icon {
713
- font-size: 14px;
714
- }
715
-
716
- .search-mode-badge {
717
- display: none;
718
- padding: 6px 12px;
719
- background: #10a37f;
720
- color: white;
721
- border-radius: 16px;
722
- font-size: 11px;
723
- font-weight: 600;
724
- text-transform: uppercase;
725
- letter-spacing: 0.5px;
726
- }
727
-
728
- .search-mode-badge.active {
729
- display: flex;
730
- align-items: center;
731
- gap: 6px;
732
- }
733
-
734
- .search-indicator {
735
- display: none;
736
- align-items: center;
737
- gap: 8px;
738
- padding: 10px 16px;
739
- background: linear-gradient(90deg, #10a37f20, #10a37f10);
740
- border: 1px solid #10a37f;
741
- border-radius: 12px;
742
- font-size: 13px;
743
- color: #10a37f;
744
- margin-bottom: 12px;
745
- animation: pulse-border 1.5s infinite;
746
- }
747
-
748
- @keyframes pulse-border {
749
- 0%, 100% { border-color: #10a37f; }
750
- 50% { border-color: #10a37f80; }
751
- }
752
-
753
- .search-indicator .spinner {
754
- width: 16px;
755
- height: 16px;
756
- border: 2px solid #10a37f40;
757
- border-top-color: #10a37f;
758
- border-radius: 50%;
759
- animation: spin 0.8s linear infinite;
760
- }
761
-
762
- @keyframes spin {
763
- to { transform: rotate(360deg); }
764
- }
765
-
766
- .search-indicator.active {
767
- display: flex;
768
- }
769
-
770
- .input-row {
771
- display: flex;
772
- gap: 8px;
773
- flex: 1;
774
- width: 100%;
775
- }
776
-
777
- #messageInput {
778
- flex: 1;
779
- padding: 12px 16px;
780
- border: 1px solid #4a4a4a;
781
- border-radius: 8px;
782
- font-size: 14px;
783
- font-family: inherit;
784
- outline: none;
785
- resize: none;
786
- min-height: 48px;
787
- max-height: 200px;
788
- transition: border-color 0.2s;
789
- background: #3a3a3a;
790
- color: #e5e5e5;
791
- }
792
-
793
- #messageInput:focus {
794
- border-color: #10a37f;
795
- }
796
-
797
- #messageInput:disabled {
798
- background: #2a2a2a;
799
- cursor: not-allowed;
800
- }
801
-
802
- #messageInput::placeholder {
803
- color: #6a6a6a;
804
- }
805
-
806
- button {
807
- padding: 12px 20px;
808
- border: none;
809
- border-radius: 8px;
810
- font-size: 14px;
811
- font-weight: 600;
812
- cursor: pointer;
813
- transition: all 0.2s;
814
- outline: none;
815
- }
816
-
817
- #sendBtn {
818
- background: #10a37f;
819
- color: white;
820
- }
821
-
822
- #sendBtn:hover:not(:disabled) {
823
- background: #0e8f6f;
824
- }
825
-
826
- #sendBtn:disabled {
827
- background: #d1d5db;
828
- cursor: not-allowed;
829
- }
830
-
831
- #stopBtn {
832
- background: #ef4444;
833
- color: white;
834
- display: none;
835
- }
836
-
837
- #stopBtn:hover {
838
- background: #dc2626;
839
- }
840
-
841
- .empty-state {
842
- text-align: center;
843
- color: #6a6a6a;
844
- margin-top: 100px;
845
- }
846
-
847
- .empty-state h2 {
848
- font-size: 24px;
849
- margin-bottom: 8px;
850
- color: #e5e5e5;
851
- }
852
-
853
- .empty-state p {
854
- font-size: 14px;
855
- }
856
-
857
- .error-message {
858
- background: #3a1a1a;
859
- color: #ff6b6b;
860
- padding: 12px 16px;
861
- border-radius: 8px;
862
- margin: 8px 0;
863
- font-size: 14px;
864
- border: 1px solid #5a2a2a;
865
- }
866
- </style>
867
-
868
- <link rel="manifest" href="manifest.json">
869
- <meta name="theme-color" content="#10a37f">
870
- <link rel="icon" href="icon.svg" type="image/svg+xml">
871
-
872
- </head>
873
- <body>
874
- <button class="toggle-sidebar" id="toggleSidebar">☰</button>
875
- <div class="sidebar-overlay" id="sidebarOverlay"></div>
876
-
877
- <div class="sidebar" id="sidebar">
878
- <div class="sidebar-header">
879
- <button class="new-chat-btn" id="newChatBtn">+ New Chat</button>
880
- <button class="close-sidebar-btn" id="closeSidebarBtn">&#10005;</button>
881
- </div>
882
- <div class="chat-history" id="chatHistory">
883
- <!-- Chat history items will be added here -->
884
- </div>
885
- <div class="sidebar-footer">
886
- <button class="clear-all-btn" id="clearAllBtn">Clear All Chats</button>
887
- </div>
888
- </div>
889
-
890
- <div class="main-content">
891
- <div class="header">
892
- <h1>Xlnk AI</h1>
893
- <div class="model-selector">
894
- <label for="modelSelect">Model:</label>
895
- <select id="modelSelect">
896
- <option value="https://xlnk-350m.hf.space/v1/chat/completions">Xlnkai 350M (Fastest)</option>
897
- <option value="https://xlnk-ai.hf.space/v1/chat/completions" selected>Xlnkai 700M (Balanced)</option>
898
- <option value="https://xlnk-corelm.hf.space/v1/chat/completions">Xlnkai 3B (Best)</option>
899
- </select>
900
- </div>
901
- </div>
902
-
903
- <div class="chat-container" id="chatContainer">
904
- <div class="empty-state">
905
- <h2>Welcome to Xlnk AI</h2>
906
- <p>Ask me anything to get started</p>
907
- </div>
908
- </div>
909
-
910
- <div class="input-container">
911
- <div class="input-wrapper">
912
- <div class="search-controls">
913
- <div class="web-search-toggle" id="webSearchToggle">
914
- <span class="search-icon">&#128269;</span>
915
- <div class="toggle-switch"></div>
916
- <span class="toggle-label">Web Search</span>
917
- </div>
918
- <div class="search-mode-badge" id="searchModeBadge">
919
- <span>&#127760;</span> Web Only Mode
920
- </div>
921
- </div>
922
- <div class="search-indicator" id="searchIndicator">
923
- <div class="spinner"></div>
924
- <span>Searching the web for real-time information...</span>
925
- </div>
926
- <div class="input-row">
927
- <textarea id="messageInput" placeholder="Type your message..." rows="1"></textarea>
928
- <button id="sendBtn">Send</button>
929
- <button id="stopBtn">Stop</button>
930
- </div>
931
- </div>
932
- </div>
933
- </div>
934
-
935
- <script>
936
- const chatContainer = document.getElementById('chatContainer');
937
- const messageInput = document.getElementById('messageInput');
938
- const sendBtn = document.getElementById('sendBtn');
939
- const stopBtn = document.getElementById('stopBtn');
940
- const modelSelect = document.getElementById('modelSelect');
941
- const webSearchToggle = document.getElementById('webSearchToggle');
942
- const searchModeBadge = document.getElementById('searchModeBadge');
943
- const chatHistoryEl = document.getElementById('chatHistory');
944
- const newChatBtn = document.getElementById('newChatBtn');
945
- const toggleSidebarBtn = document.getElementById('toggleSidebar');
946
- const closeSidebarBtn = document.getElementById('closeSidebarBtn');
947
- const sidebar = document.getElementById('sidebar');
948
- const searchIndicator = document.getElementById('searchIndicator');
949
- const clearAllBtn = document.getElementById('clearAllBtn');
950
- const sidebarOverlay = document.getElementById('sidebarOverlay');
951
-
952
- let webSearchEnabled = false;
953
-
954
- const SYSTEM_PROMPT = "You are Xlnk AI, a fast, lightweight, and helpful AI assistant.\nYou give clear, concise, and accurate answers.\nYou avoid unnecessary verbosity.\nYou explain step by step only when the user asks.\nYou are optimized for low-latency responses on llama.cpp.\nIf unsure, say you are not sure.\nNever hallucinate facts.";
955
-
956
- const SYSTEM_PROMPT_WITH_SEARCH = `You are Xlnk AI operating in WEB SEARCH ONLY MODE.
957
-
958
- CRITICAL INSTRUCTIONS:
959
- - You MUST ONLY use the web search results provided below to answer the user's question.
960
- - DO NOT use any information from your training data.
961
- - If the search results don't contain relevant information, clearly state: "The web search did not return relevant results for this query."
962
- - ALWAYS cite your sources with the exact URL from the search results.
963
- - Format citations as [Source: URL] at the end of relevant statements.
964
- - If multiple sources support a fact, cite all of them.
965
- - Be explicit about what information came from which source.
966
- - Never make up or hallucinate information not present in the search results.
967
-
968
- Your response MUST be based EXCLUSIVELY on the provided web search results.`;
969
-
970
- // Real web search using multiple APIs for best results
971
- async function performWebSearch(query) {
972
- let results = [];
973
-
974
- try {
975
- // Try Wikipedia API first - most reliable
976
- const wikiSearchUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&format=json&origin=*&srlimit=3`;
977
- const wikiSearchResponse = await fetch(wikiSearchUrl);
978
- const wikiSearchData = await wikiSearchResponse.json();
979
-
980
- if (wikiSearchData.query?.search?.length > 0) {
981
- for (const result of wikiSearchData.query.search) {
982
- const pageUrl = `https://en.wikipedia.org/wiki/${encodeURIComponent(result.title.replace(/ /g, '_'))}`;
983
- results.push({
984
- title: result.title,
985
- snippet: result.snippet.replace(/<[^>]*>/g, ''),
986
- url: pageUrl
987
- });
988
- }
989
- }
990
-
991
- // Also try to get a summary for the main topic
992
- const wikiSummaryUrl = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}`;
993
- try {
994
- const wikiResponse = await fetch(wikiSummaryUrl);
995
- if (wikiResponse.ok) {
996
- const wikiData = await wikiResponse.json();
997
- if (wikiData.extract && wikiData.type !== 'disambiguation') {
998
- // Add as first result if we got a direct match
999
- results.unshift({
1000
- title: wikiData.title || query,
1001
- snippet: wikiData.extract,
1002
- url: wikiData.content_urls?.desktop?.page || `https://en.wikipedia.org/wiki/${encodeURIComponent(query)}`
1003
- });
1004
- }
1005
- }
1006
- } catch (e) {
1007
- // Silently fail
1008
- }
1009
-
1010
- // Try DuckDuckGo Instant Answer API as additional source
1011
- try {
1012
- const ddgUrl = `https://xlnk-search.hf.space/search?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`;
1013
- const ddgResponse = await fetch(ddgUrl);
1014
- const ddgData = await ddgResponse.json();
1015
-
1016
- if (ddgData.Abstract && ddgData.AbstractURL) {
1017
- // Check if we don't already have this
1018
- const exists = results.some(r => r.url === ddgData.AbstractURL);
1019
- if (!exists) {
1020
- results.push({
1021
- title: ddgData.Heading || 'DuckDuckGo Result',
1022
- snippet: ddgData.Abstract,
1023
- url: ddgData.AbstractURL
1024
- });
1025
- }
1026
- }
1027
-
1028
- // Add related topics
1029
- if (ddgData.RelatedTopics) {
1030
- for (const topic of ddgData.RelatedTopics.slice(0, 3)) {
1031
- if (topic.Text && topic.FirstURL) {
1032
- const exists = results.some(r => r.url === topic.FirstURL);
1033
- if (!exists) {
1034
- results.push({
1035
- title: topic.Text.split(' - ')[0] || 'Related',
1036
- snippet: topic.Text,
1037
- url: topic.FirstURL
1038
- });
1039
- }
1040
- }
1041
- }
1042
- }
1043
- } catch (e) {
1044
- // Silently fail
1045
- }
1046
-
1047
- // Limit to 6 results max
1048
- results = results.slice(0, 6);
1049
-
1050
- } catch (error) {
1051
- console.error('Web search error:', error);
1052
- }
1053
-
1054
- return results;
1055
- }
1056
-
1057
- // Store last search results for display
1058
- let lastSearchResults = [];
1059
-
1060
- function formatSearchResults(results) {
1061
- lastSearchResults = results;
1062
-
1063
- if (results.length === 0) {
1064
- return "\n[WEB SEARCH RETURNED NO RESULTS]\nPlease inform the user that no web search results were found and you cannot answer this question in Web Search Only mode.\n";
1065
- }
1066
-
1067
- let formatted = "\n========== WEB SEARCH RESULTS (USE ONLY THESE) ==========\n";
1068
- results.forEach((result, index) => {
1069
- formatted += `\n--- RESULT ${index + 1} ---\n`;
1070
- formatted += `Title: ${result.title}\n`;
1071
- formatted += `Content: ${result.snippet}\n`;
1072
- if (result.url) {
1073
- formatted += `URL: ${result.url}\n`;
1074
- }
1075
- });
1076
- formatted += "\n========== END OF WEB SEARCH RESULTS ==========\n\n";
1077
- formatted += "REMEMBER: Base your answer EXCLUSIVELY on the above search results. Include clickable links using markdown format [text](url). Cite sources.\n\nUser's question: ";
1078
- return formatted;
1079
- }
1080
-
1081
- function renderSearchResultsPreview(results) {
1082
- if (results.length === 0) return '';
1083
-
1084
- let html = '<div class="search-results-preview"><strong>Sources found:</strong>';
1085
- results.forEach((result, index) => {
1086
- html += `<div class="result-item">`;
1087
- html += `<div class="result-title">${index + 1}. ${escapeHtml(result.title)}</div>`;
1088
- html += `<div class="result-url"><a href="${escapeHtml(result.url)}" target="_blank" rel="noopener">${escapeHtml(result.url)}</a></div>`;
1089
- html += `</div>`;
1090
- });
1091
- html += '</div>';
1092
- return html;
1093
- }
1094
-
1095
- let conversationHistory = [];
1096
- let abortController = null;
1097
- let isGenerating = false;
1098
- let chatSessions = [];
1099
- let currentSessionId = null;
1100
- let currentAssistantMessageDiv = null;
1101
-
1102
- // Load chat sessions from localStorage
1103
- function loadChatSessions() {
1104
- const saved = localStorage.getItem('xlnkChatSessions');
1105
- if (saved) {
1106
- chatSessions = JSON.parse(saved);
1107
- renderChatHistory();
1108
- }
1109
- }
1110
-
1111
- // Save chat sessions to localStorage
1112
- function saveChatSessions() {
1113
- localStorage.setItem('xlnkChatSessions', JSON.stringify(chatSessions));
1114
- }
1115
-
1116
- // Create new chat session
1117
- function createNewSession() {
1118
- const sessionId = Date.now().toString();
1119
- const session = {
1120
- id: sessionId,
1121
- title: 'New Chat',
1122
- messages: [],
1123
- createdAt: Date.now()
1124
- };
1125
- chatSessions.unshift(session);
1126
- currentSessionId = sessionId;
1127
- conversationHistory = [];
1128
- clearChat();
1129
- saveChatSessions();
1130
- renderChatHistory();
1131
- }
1132
-
1133
- // Load a chat session
1134
- function loadSession(sessionId) {
1135
- const session = chatSessions.find(s => s.id === sessionId);
1136
- if (session) {
1137
- currentSessionId = sessionId;
1138
- conversationHistory = [...session.messages];
1139
- clearChat();
1140
- session.messages.forEach(msg => {
1141
- addMessage(msg.role, msg.content);
1142
- });
1143
- renderChatHistory();
1144
- }
1145
- }
1146
-
1147
- // Update current session
1148
- function updateCurrentSession() {
1149
- if (!currentSessionId) {
1150
- createNewSession();
1151
- }
1152
- const session = chatSessions.find(s => s.id === currentSessionId);
1153
- if (session) {
1154
- session.messages = [...conversationHistory];
1155
- if (session.title === 'New Chat' && conversationHistory.length > 0) {
1156
- const firstUserMsg = conversationHistory.find(m => m.role === 'user');
1157
- if (firstUserMsg) {
1158
- session.title = firstUserMsg.content.substring(0, 50) + (firstUserMsg.content.length > 50 ? '...' : '');
1159
- }
1160
- }
1161
- saveChatSessions();
1162
- renderChatHistory();
1163
- }
1164
- }
1165
-
1166
- // Delete a chat session
1167
- function deleteSession(sessionId, event) {
1168
- event.stopPropagation();
1169
-
1170
- const index = chatSessions.findIndex(s => s.id === sessionId);
1171
- if (index === -1) return;
1172
-
1173
- chatSessions.splice(index, 1);
1174
- saveChatSessions();
1175
-
1176
- // If we deleted the current session, switch to another or create new
1177
- if (sessionId === currentSessionId) {
1178
- if (chatSessions.length > 0) {
1179
- loadSession(chatSessions[0].id);
1180
- } else {
1181
- createNewSession();
1182
- }
1183
- } else {
1184
- renderChatHistory();
1185
- }
1186
- }
1187
-
1188
- // Render chat history sidebar
1189
- function renderChatHistory() {
1190
- chatHistoryEl.innerHTML = '';
1191
- chatSessions.forEach(session => {
1192
- const item = document.createElement('div');
1193
- item.className = 'history-item' + (session.id === currentSessionId ? ' active' : '');
1194
-
1195
- const title = document.createElement('div');
1196
- title.className = 'history-item-title';
1197
- title.textContent = session.title;
1198
-
1199
- const deleteBtn = document.createElement('button');
1200
- deleteBtn.className = 'delete-chat-btn';
1201
- deleteBtn.textContent = '🗑️';
1202
- deleteBtn.onclick = (e) => deleteSession(session.id, e);
1203
-
1204
- item.appendChild(title);
1205
- item.appendChild(deleteBtn);
1206
- item.onclick = () => loadSession(session.id);
1207
-
1208
- chatHistoryEl.appendChild(item);
1209
- });
1210
- }
1211
-
1212
- // Clear chat display
1213
- function clearChat() {
1214
- chatContainer.innerHTML = '<div class="empty-state"><h2>Welcome to Xlnk AI</h2><p>Ask me anything to get started</p></div>';
1215
- }
1216
-
1217
- // Toggle sidebar
1218
- function openSidebar() {
1219
- sidebar.classList.add('open');
1220
- sidebarOverlay.classList.add('active');
1221
- }
1222
-
1223
- function closeSidebar() {
1224
- sidebar.classList.remove('open');
1225
- sidebarOverlay.classList.remove('active');
1226
- }
1227
-
1228
- toggleSidebarBtn.addEventListener('click', () => {
1229
- if (sidebar.classList.contains('open')) {
1230
- closeSidebar();
1231
- } else {
1232
- openSidebar();
1233
- }
1234
- });
1235
-
1236
- // Close sidebar
1237
- closeSidebarBtn.addEventListener('click', closeSidebar);
1238
-
1239
- // Close sidebar when clicking overlay
1240
- sidebarOverlay.addEventListener('click', closeSidebar);
1241
-
1242
- // Web search toggle
1243
- webSearchToggle.addEventListener('click', () => {
1244
- webSearchEnabled = !webSearchEnabled;
1245
- webSearchToggle.classList.toggle('active', webSearchEnabled);
1246
- searchModeBadge.classList.toggle('active', webSearchEnabled);
1247
-
1248
- if (webSearchEnabled) {
1249
- messageInput.placeholder = "Ask anything - I'll search the web for real-time answers...";
1250
- } else {
1251
- messageInput.placeholder = "Type your message...";
1252
- }
1253
- });
1254
-
1255
- // New chat button
1256
- newChatBtn.addEventListener('click', createNewSession);
1257
-
1258
- // Clear all chats
1259
- clearAllBtn.addEventListener('click', () => {
1260
- if (confirm('Are you sure you want to delete all chat history? This cannot be undone.')) {
1261
- chatSessions = [];
1262
- localStorage.removeItem('xlnkChatSessions');
1263
- createNewSession();
1264
- }
1265
- });
1266
-
1267
- // Initialize
1268
- loadChatSessions();
1269
- if (chatSessions.length === 0) {
1270
- createNewSession();
1271
- } else {
1272
- currentSessionId = chatSessions[0].id;
1273
- renderChatHistory();
1274
- }
1275
-
1276
- // Auto-resize textarea
1277
- messageInput.addEventListener('input', function() {
1278
- this.style.height = 'auto';
1279
- this.style.height = Math.min(this.scrollHeight, 200) + 'px';
1280
- });
1281
-
1282
- // Send on Enter, new line on Shift+Enter
1283
- messageInput.addEventListener('keydown', function(e) {
1284
- if (e.key === 'Enter' && !e.shiftKey) {
1285
- e.preventDefault();
1286
- sendMessage();
1287
- }
1288
- });
1289
-
1290
- sendBtn.addEventListener('click', sendMessage);
1291
- stopBtn.addEventListener('click', stopGeneration);
1292
-
1293
- function clearEmptyState() {
1294
- const emptyState = chatContainer.querySelector('.empty-state');
1295
- if (emptyState) {
1296
- emptyState.remove();
1297
- }
1298
- }
1299
-
1300
- // Simple markdown renderer with link support
1301
- function renderMarkdown(text, includeCursor = false) {
1302
- // Remove existing cursor if present
1303
- text = text.replace(/<span class="typing-cursor"><img[^>]*><\/span>/g, '');
1304
-
1305
- // Code blocks
1306
- text = text.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
1307
- return `<pre><code>${escapeHtml(code.trim())}</code></pre>`;
1308
- });
1309
-
1310
- // Inline code
1311
- text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
1312
-
1313
- // Links [text](url)
1314
- text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
1315
-
1316
- // Plain URLs - make them clickable
1317
- text = text.replace(/(https?:\/\/[^\s<]+)/g, (match) => {
1318
- // Don't double-wrap URLs that are already in links
1319
- if (text.indexOf(`href="${match}"`) !== -1) return match;
1320
- return `<a href="${match}" target="_blank" rel="noopener">${match}</a>`;
1321
- });
1322
-
1323
- // Bold
1324
- text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
1325
-
1326
- // Italic
1327
- text = text.replace(/\*(.+?)\*/g, '<em>$1</em>');
1328
-
1329
- // Line breaks
1330
- text = text.replace(/\n/g, '<br>');
1331
-
1332
- // Add typing cursor at the end if requested
1333
- if (includeCursor && isGenerating) {
1334
- text += '<span class="typing-cursor"><img src="https://me.xo.je/11.webp" alt="Typing cursor"></span>';
1335
- }
1336
-
1337
- return text;
1338
- }
1339
-
1340
- function escapeHtml(text) {
1341
- const div = document.createElement('div');
1342
- div.textContent = text;
1343
- return div.innerHTML;
1344
- }
1345
-
1346
- function addMessage(role, content, isStreaming = false) {
1347
- clearEmptyState();
1348
-
1349
- const messageDiv = document.createElement('div');
1350
- messageDiv.className = `message ${role}`;
1351
- if (role === 'assistant' && isStreaming) {
1352
- messageDiv.classList.add('thinking');
1353
- }
1354
-
1355
- const avatar = document.createElement('div');
1356
- avatar.className = 'avatar';
1357
-
1358
- if (role === 'user') {
1359
- avatar.textContent = 'U';
1360
- } else {
1361
- // Use the provided webp image for AI assistant with green animation
1362
- const img = document.createElement('img');
1363
- img.src = 'https://me.xo.je/11.webp';
1364
- img.alt = 'Xlnk AI';
1365
- avatar.appendChild(img);
1366
-
1367
- // Store reference to current assistant message
1368
- currentAssistantMessageDiv = messageDiv;
1369
- }
1370
-
1371
- const contentDiv = document.createElement('div');
1372
- contentDiv.className = 'message-content';
1373
-
1374
- if (role === 'assistant') {
1375
- if (isStreaming) {
1376
- // Add initial centered typing cursor
1377
- contentDiv.innerHTML = '<span class="typing-cursor"><img src="https://me.xo.je/11.webp" alt="Typing cursor"></span>';
1378
- } else {
1379
- contentDiv.innerHTML = renderMarkdown(content);
1380
- }
1381
- } else {
1382
- contentDiv.textContent = content;
1383
- }
1384
-
1385
- messageDiv.appendChild(avatar);
1386
- messageDiv.appendChild(contentDiv);
1387
- chatContainer.appendChild(messageDiv);
1388
-
1389
- scrollToBottom();
1390
-
1391
- return contentDiv;
1392
- }
1393
-
1394
- function scrollToBottom() {
1395
- chatContainer.scrollTop = chatContainer.scrollHeight;
1396
- }
1397
-
1398
- function showError(message) {
1399
- const errorDiv = document.createElement('div');
1400
- errorDiv.className = 'error-message';
1401
- errorDiv.textContent = message;
1402
- chatContainer.appendChild(errorDiv);
1403
- scrollToBottom();
1404
- }
1405
-
1406
- function updateButtonStates(generating) {
1407
- isGenerating = generating;
1408
- sendBtn.disabled = generating;
1409
- messageInput.disabled = generating;
1410
- sendBtn.style.display = generating ? 'none' : 'block';
1411
- stopBtn.style.display = generating ? 'block' : 'none';
1412
-
1413
- // Update thinking animation
1414
- if (currentAssistantMessageDiv) {
1415
- if (generating) {
1416
- currentAssistantMessageDiv.classList.add('thinking');
1417
- } else {
1418
- currentAssistantMessageDiv.classList.remove('thinking');
1419
- }
1420
- }
1421
- }
1422
-
1423
- function stopGeneration() {
1424
- if (abortController) {
1425
- abortController.abort();
1426
- abortController = null;
1427
- }
1428
- updateButtonStates(false);
1429
- }
1430
-
1431
- async function sendMessage() {
1432
- const userMessage = messageInput.value.trim();
1433
- if (!userMessage || isGenerating) return;
1434
-
1435
- const useWebSearch = webSearchEnabled;
1436
-
1437
- // Add user message to UI and history
1438
- addMessage('user', userMessage);
1439
- conversationHistory.push({
1440
- role: 'user',
1441
- content: userMessage
1442
- });
1443
-
1444
- // Clear input
1445
- messageInput.value = '';
1446
- messageInput.style.height = 'auto';
1447
-
1448
- updateButtonStates(true);
1449
-
1450
- // Create abort controller for this request
1451
- abortController = new AbortController();
1452
-
1453
- // Perform web search if enabled
1454
- let searchResults = '';
1455
- let searchResultsForDisplay = [];
1456
- if (useWebSearch) {
1457
- searchIndicator.classList.add('active');
1458
- const results = await performWebSearch(userMessage);
1459
- searchResultsForDisplay = results;
1460
- searchResults = formatSearchResults(results);
1461
- searchIndicator.classList.remove('active');
1462
-
1463
- // Show sources found message
1464
- if (results.length > 0) {
1465
- const sourcesDiv = document.createElement('div');
1466
- sourcesDiv.className = 'message assistant';
1467
- sourcesDiv.innerHTML = `
1468
- <div class="avatar"><img src="https://me.xo.je/11.webp" alt="Xlnk AI"></div>
1469
- <div class="message-content">
1470
- ${renderSearchResultsPreview(results)}
1471
- </div>
1472
- `;
1473
- clearEmptyState();
1474
- chatContainer.appendChild(sourcesDiv);
1475
- scrollToBottom();
1476
- }
1477
- }
1478
-
1479
- // Prepare messages with system prompt
1480
- const systemPrompt = useWebSearch ? SYSTEM_PROMPT_WITH_SEARCH : SYSTEM_PROMPT;
1481
- let userContent = userMessage;
1482
-
1483
- if (useWebSearch) {
1484
- userContent = searchResults + userMessage;
1485
- }
1486
-
1487
- const messages = [
1488
- { role: 'system', content: systemPrompt },
1489
- ...conversationHistory.slice(0, -1),
1490
- { role: 'user', content: userContent }
1491
- ];
1492
-
1493
- // Create assistant message container
1494
- const assistantContent = addMessage('assistant', '', true);
1495
- let fullResponse = '';
1496
-
1497
- try {
1498
- const endpoint = modelSelect.value;
1499
- const response = await fetch(endpoint, {
1500
- method: 'POST',
1501
- headers: {
1502
- 'Content-Type': 'application/json',
1503
- },
1504
- body: JSON.stringify({
1505
- messages: messages,
1506
- stream: true,
1507
- max_tokens: -1,
1508
- temperature: 0,
1509
- top_p: 0
1510
- }),
1511
- signal: abortController.signal
1512
- });
1513
-
1514
- if (!response.ok) {
1515
- throw new Error(`HTTP error! status: ${response.status}`);
1516
- }
1517
-
1518
- const reader = response.body.getReader();
1519
- const decoder = new TextDecoder();
1520
-
1521
- while (true) {
1522
- const { done, value } = await reader.read();
1523
- if (done) break;
1524
-
1525
- const chunk = decoder.decode(value, { stream: true });
1526
- const lines = chunk.split('\n');
1527
-
1528
- for (const line of lines) {
1529
- if (line.startsWith('data: ')) {
1530
- const data = line.slice(6);
1531
- if (data === '[DONE]') continue;
1532
-
1533
- try {
1534
- const parsed = JSON.parse(data);
1535
- const token = parsed.choices?.[0]?.delta?.content;
1536
-
1537
- if (token) {
1538
- fullResponse += token;
1539
- assistantContent.innerHTML = renderMarkdown(fullResponse, true);
1540
- scrollToBottom();
1541
- }
1542
- } catch (e) {
1543
- // Skip invalid JSON
1544
- }
1545
- }
1546
- }
1547
- }
1548
-
1549
- // Remove cursor when done
1550
- assistantContent.innerHTML = renderMarkdown(fullResponse, false);
1551
-
1552
- // Add to conversation history
1553
- conversationHistory.push({
1554
- role: 'assistant',
1555
- content: fullResponse
1556
- });
1557
-
1558
- // Update session
1559
- updateCurrentSession();
1560
-
1561
- } catch (error) {
1562
- if (error.name === 'AbortError') {
1563
- assistantContent.innerHTML = renderMarkdown(fullResponse + ' [Stopped]');
1564
- conversationHistory.push({
1565
- role: 'assistant',
1566
- content: fullResponse
1567
- });
1568
- updateCurrentSession();
1569
- } else {
1570
- console.error('Error:', error);
1571
- assistantContent.remove();
1572
- showError(`Error: ${error.message}. Please try again.`);
1573
- conversationHistory.pop(); // Remove user message if failed
1574
- }
1575
- } finally {
1576
- updateButtonStates(false);
1577
- abortController = null;
1578
- currentAssistantMessageDiv = null;
1579
- }
1580
- }
1581
- </script>
1582
-
1583
- <script>
1584
- if ("serviceWorker" in navigator) {
1585
- window.addEventListener("load", () => {
1586
- navigator.serviceWorker.register("sw.js");
1587
- });
1588
- }
1589
- </script>
1590
-
1591
- </body>
1592
- </html>