ParthSadaria commited on
Commit
dd97c12
·
verified ·
1 Parent(s): cb01ce9

Update playground.html

Browse files
Files changed (1) hide show
  1. playground.html +259 -861
playground.html CHANGED
@@ -1,779 +1,159 @@
1
  <!DOCTYPE html>
2
- <html lang="en">
3
-
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Loki.AI Playground</title>
8
- <link rel="icon" type="image/x-icon" href="favicon.ico">
9
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
10
  <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
11
- <script src="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.js"></script>
12
- <style>
13
- :root {
14
- --bg-dark: #0a0a0f;
15
- --bg-darker: #040409;
16
- --bg-deepest: #020205;
17
- --primary-blue: #4a6cf7;
18
- --secondary-blue: #6678e3;
19
- --accent-color: #7e57c2;
20
- --text-light: #e0e0e8;
21
- --text-muted: #8a8a9b;
22
- --border-color: #1a1a2e;
23
- --hover-color: rgba(78, 108, 247, 0.1);
24
- --delete-red: #ff4d4d;
25
- --header-height: 60px;
26
- --input-height: 80px;
27
- }
28
-
29
- * {
30
- margin: 0;
31
- padding: 0;
32
- box-sizing: border-box;
33
- scrollbar-width: thin;
34
- scrollbar-color: var(--primary-blue) transparent;
35
- }
36
-
37
- *::-webkit-scrollbar {
38
- width: 8px;
39
- }
40
-
41
- *::-webkit-scrollbar-track {
42
- background: transparent;
43
- }
44
-
45
- *::-webkit-scrollbar-thumb {
46
- background-color: var(--primary-blue);
47
- border-radius: 20px;
48
- }
49
-
50
- body {
51
- font-family: 'Inter', sans-serif;
52
- background-color: var(--bg-dark);
53
- color: var(--text-light);
54
- display: flex;
55
- justify-content: center;
56
- align-items: center;
57
- min-height: 100vh;
58
- overflow: hidden;
59
- perspective: 2000px;
60
- }
61
-
62
- .chat-wrapper {
63
- position: relative;
64
- width: 100%;
65
- max-width: 1000px;
66
- height: 90vh;
67
- background-color: var(--bg-darker);
68
- border-radius: 24px;
69
- overflow: hidden;
70
- box-shadow: 0 20px 50px rgba(5, 5, 10, 0.7);
71
- display: flex;
72
- flex-direction: column;
73
- opacity: 1;
74
- transform: scale(1);
75
- transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
76
- border: 1px solid var(--border-color);
77
- }
78
-
79
- .chat-header {
80
- background-color: var(--bg-deepest);
81
- padding: 15px 20px;
82
- display: flex;
83
- justify-content: space-between;
84
- align-items: center;
85
- border-bottom: 1px solid var(--border-color);
86
- flex-shrink: 0;
87
- height: var(--header-height);
88
- }
89
-
90
- .chat-container {
91
- display: none;
92
- flex-direction: column;
93
- height: calc(100% - var(--header-height));
94
- position: relative;
95
- }
96
-
97
- .chat-messages {
98
- flex: 1;
99
- overflow-y: auto;
100
- padding: 20px;
101
- display: flex;
102
- flex-direction: column;
103
- gap: 16px;
104
- background-color: var(--bg-dark);
105
- font-family: 'JetBrains Mono', monospace;
106
- height: calc(100% - var(--input-height));
107
- margin-bottom: var(--input-height);
108
- }
109
-
110
- .chat-input {
111
- position: absolute;
112
- bottom: 0;
113
- left: 0;
114
- right: 0;
115
- background-color: var(--bg-darker);
116
- border-top: 1px solid var(--border-color);
117
- padding: 16px;
118
- height: var(--input-height);
119
- display: flex;
120
- gap: 12px;
121
- z-index: 10;
122
- align-items: center;
123
- }
124
-
125
- .chat-input-container {
126
- position: relative;
127
- flex: 1;
128
- display: flex;
129
- align-items: center;
130
- }
131
-
132
- .chat-input input {
133
- width: 100%;
134
- padding: 12px 16px;
135
- padding-right: 50px;
136
- background-color: rgba(30, 30, 50, 0.8);
137
- border: 1px solid var(--border-color);
138
- border-radius: 12px;
139
- color: var(--text-light);
140
- font-size: 14px;
141
- font-family: 'JetBrains Mono', monospace;
142
- outline: none;
143
- }
144
-
145
- .chat-input input:focus {
146
- border-color: var(--primary-blue);
147
- box-shadow: 0 0 0 3px rgba(74, 108, 247, 0.2);
148
- }
149
-
150
- .message {
151
- max-width: 80%;
152
- width: fit-content;
153
- padding: 12px 18px;
154
- border-radius: 12px;
155
- font-size: 14px;
156
- line-height: 1.5;
157
- position: relative;
158
- animation: fadeIn 0.4s forwards;
159
- margin-bottom: 8px;
160
- }
161
-
162
- .message.user {
163
- align-self: flex-end;
164
- background-color: var(--primary-blue);
165
- color: white;
166
- }
167
-
168
- .message.bot {
169
- align-self: flex-start;
170
- background-color: rgba(40, 40, 70, 0.8);
171
- color: var(--text-light);
172
- border: 1px solid var(--border-color);
173
- }
174
-
175
- .message::before {
176
- content: '';
177
- position: absolute;
178
- top: 0;
179
- left: 0;
180
- right: 0;
181
- bottom: 0;
182
- background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.1));
183
- opacity: 0;
184
- transition: opacity 0.3s ease;
185
- }
186
-
187
- .message:hover::before {
188
- opacity: 1;
189
- }
190
-
191
- .initial-input {
192
- flex: 1;
193
- display: flex;
194
- flex-direction: column;
195
- justify-content: center;
196
- align-items: center;
197
- padding: 24px;
198
- text-align: center;
199
- background: linear-gradient(145deg, var(--bg-dark), var(--bg-darker));
200
- }
201
-
202
- .initial-input h2 {
203
- font-size: 24px;
204
- margin-bottom: 16px;
205
- color: var(--text-light);
206
- font-weight: 600;
207
- font-family: 'JetBrains Mono', monospace;
208
- }
209
-
210
- .input-container {
211
- width: 100%;
212
- max-width: 400px;
213
- position: relative;
214
- }
215
-
216
- .initial-input input {
217
- width: 100%;
218
- padding: 16px 24px;
219
- background-color: rgba(30, 30, 50, 0.8);
220
- border: 1px solid var(--border-color);
221
- border-radius: 12px;
222
- color: var(--text-light);
223
- font-size: 16px;
224
- font-family: 'JetBrains Mono', monospace;
225
- outline: none;
226
- transition: all 0.3s ease;
227
- }
228
-
229
- .send-icon {
230
- position: absolute;
231
- right: 12px;
232
- top: 50%;
233
- transform: translateY(-50%);
234
- background-color: var(--primary-blue);
235
- border-radius: 8px;
236
- width: 40px;
237
- height: 40px;
238
- display: flex;
239
- justify-content: center;
240
- align-items: center;
241
- cursor: pointer;
242
- transition: all 0.3s ease;
243
- }
244
-
245
- .send-icon:hover {
246
- background-color: var(--accent-color);
247
- transform: translateY(-50%) scale(1.05);
248
- border-radius: 15px;
249
- }
250
-
251
- @keyframes fadeIn {
252
- from {
253
- opacity: 0;
254
- transform: translateY(20px);
255
- }
256
-
257
- to {
258
- opacity: 1;
259
- transform: translateY(0);
260
- }
261
- }
262
-
263
- /* Header actions */
264
- .header-actions {
265
- display: flex;
266
- align-items: center;
267
- gap: 10px;
268
- justify-content: center;
269
- }
270
-
271
- .model-select {
272
- background-color: black;
273
- color: var(--text-light);
274
- border: 1px solid var(--border-color);
275
- border-radius: 8px;
276
- padding: 8px 12px;
277
- font-family: 'JetBrains Mono', monospace;
278
- font-size: 14px;
279
- cursor: pointer;
280
- transition: all 0.3s ease;
281
- max-width: 240px;
282
- overflow-y: auto;
283
- -webkit-appearance: none;
284
- -moz-appearance: none;
285
- appearance: none;
286
- background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2214%22%20height%3D%2214%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E");
287
- background-repeat: no-repeat;
288
- background-position: right 8px center;
289
- padding-right: 28px;
290
- }
291
-
292
- .model-select option {
293
- background-color: var(--bg-dark);
294
- color: var(--text-light);
295
- padding: 8px;
296
- }
297
-
298
- .clear-chat {
299
- background-color: var(--delete-red);
300
- color: white;
301
- border: none;
302
- border-radius: 8px;
303
- width: 36px;
304
- height: 36px;
305
- padding: 8px;
306
- display: flex;
307
- align-items: center;
308
- justify-content: center;
309
- cursor: pointer;
310
- transition: all 0.3s ease;
311
- }
312
-
313
- .clear-chat:hover {
314
- background-color: #ff6666;
315
- }
316
-
317
- .clear-chat svg {
318
- width: 18px;
319
- height: 18px;
320
- }
321
-
322
- .watermark {
323
- position: absolute;
324
- bottom: 10px;
325
- right: 15px;
326
- color: var(--text-muted);
327
- font-size: 10px;
328
- opacity: 0.5;
329
- transition: opacity 0.3s ease;
330
- z-index: 11;
331
- }
332
-
333
- .watermark:hover {
334
- opacity: 0.8;
335
- }
336
-
337
- /* Loading spinner */
338
- .loader {
339
- display: none;
340
- width: 24px;
341
- height: 24px;
342
- border: 3px solid rgba(255, 255, 255, 0.3);
343
- border-radius: 50%;
344
- border-top-color: var(--primary-blue);
345
- animation: spin 1s ease-in-out infinite;
346
- }
347
-
348
- @keyframes spin {
349
- to {
350
- transform: rotate(360deg);
351
- }
352
- }
353
-
354
- .github-link {
355
- display: flex;
356
- align-items: center;
357
- justify-content: center;
358
- width: 36px;
359
- height: 36px;
360
- z-index: 999;
361
- border-radius: 8px;
362
- background-color: transparent;
363
- border: 1px solid var(--border-color);
364
- transition: all 0.3s ease;
365
- color: var(--text-light);
366
- position: absolute;
367
- bottom: 0;
368
- left: 0;
369
- margin: 10px;
370
- }
371
-
372
- .github-link:hover {
373
- background-color: var(--hover-color);
374
- transform: translateY(-2px);
375
- }
376
-
377
- .github-link svg {
378
- width: 22px;
379
- height: 22px;
380
- }
381
-
382
- /* Enhanced select dropdown */
383
- .custom-select-wrapper {
384
- position: relative;
385
- min-width: 180px;
386
- }
387
-
388
- .custom-select {
389
- position: relative;
390
- background-color: rgba(20, 20, 40, 0.9);
391
- color: var(--text-light);
392
- border: 1px solid var(--border-color);
393
- border-radius: 8px;
394
- padding: 8px 35px 8px 12px;
395
- font-family: 'JetBrains Mono', monospace;
396
- font-size: 14px;
397
- cursor: pointer;
398
- transition: all 0.3s ease;
399
- text-overflow: ellipsis;
400
- overflow: hidden;
401
- white-space: nowrap;
402
- max-width: 240px;
403
- }
404
-
405
- .custom-select:after {
406
- content: '';
407
- position: absolute;
408
- right: 10px;
409
- top: 50%;
410
- transform: translateY(-50%);
411
- width: 0;
412
- height: 0;
413
- border-left: 5px solid transparent;
414
- border-right: 5px solid transparent;
415
- border-top: 5px solid var(--text-light);
416
- }
417
-
418
- .custom-select-options {
419
- position: absolute;
420
- top: 100%;
421
- left: 0;
422
- right: 0;
423
- background-color: rgba(15, 15, 30, 0.95);
424
- border: 1px solid var(--border-color);
425
- border-radius: 8px;
426
- margin-top: 4px;
427
- max-height: 300px;
428
- overflow-y: auto;
429
- z-index: 1000;
430
- display: none;
431
- backdrop-filter: blur(10px);
432
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
433
- }
434
-
435
- .custom-option {
436
- padding: 8px 12px;
437
- cursor: pointer;
438
- transition: all 0.2s ease;
439
- }
440
-
441
- .custom-option:hover {
442
- background-color: rgba(74, 108, 247, 0.2);
443
- }
444
-
445
- .model-group {
446
- padding: 5px 10px;
447
- font-size: 10px;
448
- color: var(--text-muted);
449
- text-transform: uppercase;
450
- letter-spacing: 1px;
451
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
452
- margin-top: 5px;
453
- }
454
-
455
- /* Dark mode toggle */
456
- .theme-toggle {
457
- background-color: transparent;
458
- border: 1px solid var(--border-color);
459
- border-radius: 8px;
460
- width: 36px;
461
- height: 36px;
462
- display: flex;
463
- align-items: center;
464
- justify-content: center;
465
- cursor: pointer;
466
- transition: all 0.3s ease;
467
- color: var(--text-light);
468
- }
469
-
470
- .theme-toggle:hover {
471
- background-color: var(--hover-color);
472
- }
473
-
474
- /* Mobile optimizations */
475
- @media screen and (max-width: 768px) {
476
- .chat-wrapper {
477
- height: 100vh;
478
- border-radius: 0;
479
- }
480
-
481
- .custom-select {
482
- font-size: 12px;
483
- max-width: 140px;
484
- }
485
-
486
- .chat-input {
487
- padding: 10px;
488
- }
489
- }
490
-
491
- /* Markdown styling */
492
- .bot pre {
493
- background-color: rgba(15, 15, 30, 0.6);
494
- padding: 10px;
495
- border-radius: 6px;
496
- overflow-x: auto;
497
- margin: 10px 0;
498
- border: 1px solid rgba(255, 255, 255, 0.1);
499
- }
500
-
501
- .bot code {
502
- font-family: 'JetBrains Mono', monospace;
503
- font-size: 12px;
504
- }
505
-
506
- .bot p {
507
- margin-bottom: 8px;
508
- }
509
-
510
- .bot ul, .bot ol {
511
- margin-left: 20px;
512
- margin-bottom: 8px;
513
- }
514
-
515
- /* Typing indicator */
516
- .typing-indicator {
517
- display: inline-flex;
518
- align-items: center;
519
- gap: 5px;
520
- padding: 5px 10px;
521
- }
522
-
523
- .typing-indicator span {
524
- width: 6px;
525
- height: 6px;
526
- background-color: var(--text-muted);
527
- border-radius: 50%;
528
- display: inline-block;
529
- animation: pulse 1.5s infinite ease-in-out;
530
- }
531
-
532
- .typing-indicator span:nth-child(2) {
533
- animation-delay: 0.2s;
534
- }
535
-
536
- .typing-indicator span:nth-child(3) {
537
- animation-delay: 0.4s;
538
- }
539
-
540
- @keyframes pulse {
541
- 0%, 100% {
542
- transform: scale(0.8);
543
- opacity: 0.5;
544
- }
545
- 50% {
546
- transform: scale(1.2);
547
- opacity: 1;
548
  }
549
  }
550
- /* markdown */
551
- /* Add this to your style section */
552
- .message pre {
553
- background-color: rgba(15, 15, 30, 0.6);
554
- padding: 10px;
555
- border-radius: 6px;
556
- overflow-x: auto;
557
- margin: 10px 0;
558
- border: 1px solid rgba(255, 255, 255, 0.1);
559
- }
560
-
561
- .message code {
562
- font-family: 'JetBrains Mono', monospace;
563
- font-size: 12px;
564
- background-color: rgba(30, 30, 50, 0.3);
565
- padding: 2px 4px;
566
- border-radius: 3px;
567
- }
568
-
569
- .message pre code {
570
- background-color: transparent;
571
- padding: 0;
572
- }
573
-
574
- .message blockquote {
575
- border-left: 3px solid var(--primary-blue);
576
- padding-left: 10px;
577
- margin: 10px 0;
578
- color: var(--text-muted);
579
- }
580
-
581
- .message h1, .message h2, .message h3 {
582
- margin: 15px 0 5px 0;
583
- }
584
-
585
- .message ul, .message ol {
586
- margin-left: 20px;
587
- margin-bottom: 8px;
588
- }
589
-
590
- .message a {
591
- color: var(--primary-blue);
592
- text-decoration: underline;
593
- }
594
-
595
- .message p {
596
- margin-bottom: 8px;
597
- }
598
-
599
- .message img {
600
- max-width: 100%;
601
- border-radius: 8px;
602
- margin: 10px 0;
603
- }
604
-
605
- .message table {
606
- border-collapse: collapse;
607
- width: 100%;
608
- margin: 15px 0;
609
- }
610
-
611
- .message th, .message td {
612
- border: 1px solid var(--border-color);
613
- padding: 8px;
614
- text-align: left;
615
- }
616
-
617
- .message th {
618
- background-color: rgba(15, 15, 30, 0.6);
619
- }
620
-
621
- /* Tablet adjustments */
622
- @media screen and (min-width: 769px) and (max-width: 1024px) {
623
- .chat-wrapper {
624
- max-width: 90%;
625
- }
626
- .chat-header {
627
- padding: 12px 18px;
628
- height: calc(var(--header-height) - 10px);
629
- }
630
- .chat-input {
631
- padding: 12px;
632
- height: calc(var(--input-height) - 10px);
633
- }
634
- }
635
-
636
- /* Mobile adjustments */
637
- @media screen and (max-width: 768px) {
638
- .chat-wrapper {
639
- height: 100vh;
640
- border-radius: 0;
641
- }
642
- .chat-header {
643
- padding: 10px 15px;
644
- height: 50px;
645
- }
646
- .chat-messages {
647
- padding: 10px;
648
- font-size: 14px;
649
- margin-bottom: calc(var(--input-height) - 20px);
650
- }
651
- .chat-input {
652
- padding: 8px;
653
- height: 60px;
654
- }
655
- .chat-input input {
656
- font-size: 14px;
657
- padding: 10px;
658
- }
659
- .model-select {
660
- font-size: 12px;
661
- max-width: 140px;
662
- }
663
- }
664
-
665
- /* Extra small devices */
666
- @media screen and (max-width: 480px) {
667
- .chat-header {
668
- font-size: 13px;
669
- padding: 8px 10px;
670
- height: 45px;
671
- }
672
- .chat-messages {
673
- padding: 8px;
674
- font-size: 12px;
675
- }
676
- .chat-input {
677
- padding: 6px;
678
- height: 50px;
679
- }
680
- .chat-input input {
681
- font-size: 12px;
682
- padding: 8px;
683
- }
684
- }
685
-
686
- </style>
687
  </head>
688
 
689
- <body>
690
- <a href="https://github.com/ParthSadaria" target="_blank" rel="noopener noreferrer" class="github-link" aria-label="Visit Parth Sadaria's GitHub profile">
691
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
 
 
 
 
692
  <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
693
  </svg>
694
  </a>
695
- <div class="chat-wrapper">
696
- <div class="chat-header">
697
- <h2 style="font-family: 'JetBrains Mono', monospace;">LOKI.AI Playground</h2>
698
- <div class="header-actions">
699
- <div class="custom-select-wrapper">
700
- <div class="custom-select" id="modelSelectDisplay">Select Model</div>
701
- <div class="custom-select-options" id="modelOptions">
702
- <!-- Models will be loaded here -->
703
- <div class="loader" id="modelLoader" style="margin: 10px auto;"></div>
 
 
 
 
 
 
 
 
704
  </div>
705
  </div>
706
- <button id="clearChatButton" class="clear-chat">
707
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
708
- stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
709
- <polyline points="3 6 5 6 21 6"></polyline>
710
- <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
711
- <line x1="10" y1="11" x2="10" y2="17"></line>
712
- <line x1="14" y1="11" x2="14" y2="17"></line>
713
  </svg>
714
  </button>
715
- <button id="themeToggle" class="theme-toggle">
716
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
717
- <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
 
 
718
  </svg>
719
  </button>
720
  </div>
721
  </div>
722
 
723
- <div class="initial-input">
724
- <h2>Welcome to LOKI.AI</h2>
725
- <div class="input-container">
726
- <input type="text" id="initialChatInput" placeholder="What can I help you with today?">
727
- <div class="send-icon" id="initialSendIcon">
728
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
729
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
730
- <line x1="22" y1="2" x2="11" y2="13"></line>
731
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
 
732
  </svg>
733
- </div>
734
  </div>
735
  </div>
736
 
737
- <div class="chat-container" id="chatContainer">
738
- <div class="chat-messages" id="chatMessages"></div>
739
- <div class="chat-input">
740
- <div class="chat-input-container">
741
- <input type="text" id="chatInput" placeholder="Type your message...">
742
- <div class="send-icon" id="sendButtonIcon">
743
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
744
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
745
- <line x1="22" y1="2" x2="11" y2="13"></line>
746
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
 
 
 
 
747
  </svg>
748
- <div class="loader" id="sendLoader"></div>
749
- </div>
 
 
750
  </div>
 
751
  </div>
752
- <p style="display: block; text-align: center; color: grey; font-size: 9px; z-index: 99; padding-bottom:5px;">
753
- Models can make mistakes. Check important info.
754
- </p>
755
  </div>
756
  </div>
757
- <div class="watermark">
 
 
758
  Made with ❤️ by Parth Sadaria
759
  </div>
 
760
  <script>
761
  document.addEventListener('DOMContentLoaded', function() {
762
- const chatWrapper = document.querySelector('.chat-wrapper');
763
- const initialInput = document.querySelector('.initial-input');
 
764
  const chatContainer = document.getElementById('chatContainer');
765
  const initialChatInput = document.getElementById('initialChatInput');
766
  const initialSendIcon = document.getElementById('initialSendIcon');
767
  const chatMessages = document.getElementById('chatMessages');
768
  const chatInput = document.getElementById('chatInput');
769
- const sendButtonIcon = document.getElementById('sendButtonIcon');
 
770
  const sendLoader = document.getElementById('sendLoader');
771
  const clearChatButton = document.getElementById('clearChatButton');
 
772
  const modelSelectDisplay = document.getElementById('modelSelectDisplay');
773
  const modelOptions = document.getElementById('modelOptions');
774
  const modelLoader = document.getElementById('modelLoader');
775
  const themeToggle = document.getElementById('themeToggle');
776
 
 
777
  let currentStreamingMessage = null;
778
  let isStreamingInProgress = false;
779
  let conversationHistory = [{ role: "system", content: "You are a helpful AI assistant. Assist the user effectively." }];
@@ -781,9 +161,17 @@ document.addEventListener('DOMContentLoaded', function() {
781
  let modelsList = [];
782
  let isDarkMode = true;
783
 
 
 
 
 
 
 
 
 
 
784
  // Fetch models from API
785
  async function fetchModels() {
786
- modelLoader.style.display = 'block';
787
  try {
788
  const response = await fetch('https://parthsadaria-lokiai.hf.space/models', {
789
  method: 'GET',
@@ -797,8 +185,6 @@ document.addEventListener('DOMContentLoaded', function() {
797
  modelsList = data;
798
  populateModels(data);
799
  } else {
800
- console.error('Failed to fetch models:', response.status);
801
- // Fallback to some default models
802
  const fallbackModels = [
803
  { id: "gpt-4o", object: "model" },
804
  { id: "gpt-3.5-turbo", object: "model" },
@@ -810,7 +196,6 @@ document.addEventListener('DOMContentLoaded', function() {
810
  }
811
  } catch (error) {
812
  console.error('Error fetching models:', error);
813
- // Use the same fallback
814
  const fallbackModels = [
815
  { id: "gpt-4o", object: "model" },
816
  { id: "gpt-3.5-turbo", object: "model" },
@@ -819,8 +204,6 @@ document.addEventListener('DOMContentLoaded', function() {
819
  { id: "llama-3.1-70b", object: "model" }
820
  ];
821
  populateModels(fallbackModels);
822
- } finally {
823
- modelLoader.style.display = 'none';
824
  }
825
  }
826
 
@@ -847,28 +230,28 @@ document.addEventListener('DOMContentLoaded', function() {
847
  modelOptions.innerHTML = '';
848
 
849
  // Add special option for web search
850
- const webSearchOption = document.createElement('div');
851
- webSearchOption.className = 'custom-option';
852
- webSearchOption.textContent = 'SearchGPT (Web-access)';
853
- webSearchOption.dataset.value = 'searchgpt';
854
- modelOptions.appendChild(webSearchOption);
855
 
856
  // Add a separator
857
  const separator = document.createElement('div');
858
- separator.className = 'model-group';
859
  separator.textContent = 'AI Models';
860
  modelOptions.appendChild(separator);
861
 
862
  // Create and append provider groups and their models
863
  Object.keys(providers).sort().forEach(provider => {
864
  const providerGroup = document.createElement('div');
865
- providerGroup.className = 'model-group';
866
  providerGroup.textContent = provider;
867
  modelOptions.appendChild(providerGroup);
868
 
869
  providers[provider].sort((a, b) => a.id.localeCompare(b.id)).forEach(model => {
870
  const option = document.createElement('div');
871
- option.className = 'custom-option';
872
  option.textContent = model.id;
873
  option.dataset.value = model.id;
874
  modelOptions.appendChild(option);
@@ -897,12 +280,14 @@ document.addEventListener('DOMContentLoaded', function() {
897
  }
898
 
899
  // Add event listeners to options
900
- document.querySelectorAll('.custom-option').forEach(option => {
901
- option.addEventListener('click', function() {
902
- selectedModel = this.dataset.value;
903
- modelSelectDisplay.textContent = this.textContent;
904
- modelOptions.style.display = 'none';
905
- });
 
 
906
  });
907
  }
908
 
@@ -910,11 +295,26 @@ document.addEventListener('DOMContentLoaded', function() {
910
  chatMessages.scrollTop = chatMessages.scrollHeight;
911
  }
912
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
  function appendMessage(content, type = 'bot', isStreaming = false) {
914
  if (type === 'bot' && isStreaming) {
915
  if (!currentStreamingMessage) {
916
- currentStreamingMessage = document.createElement('div');
917
- currentStreamingMessage.className = `message ${type}`;
918
  chatMessages.appendChild(currentStreamingMessage);
919
 
920
  // Add to conversation history only at the start of streaming
@@ -927,21 +327,13 @@ document.addEventListener('DOMContentLoaded', function() {
927
  }
928
  }
929
 
930
- // Replace the existing formatting code in the appendMessage function
931
  const formattedContent = content
932
- .replace(/```([\s\S]*?)```/g, (match, code) => `<pre><code>${code}</code></pre>`)
933
- .replace(/`([^`]+)`/g, '<code>$1</code>')
934
- .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
935
- .replace(/\*(.*?)\*/g, '<em>$1</em>')
936
- .replace(/~~(.*?)~~/g, '<del>$1</del>')
937
- .replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
938
- .replace(/^#{3}\s+(.*?)$/gm, '<h3>$1</h3>')
939
- .replace(/^#{2}\s+(.*?)$/gm, '<h2>$1</h2>')
940
- .replace(/^#{1}\s+(.*?)$/gm, '<h1>$1</h1>')
941
- .replace(/^\>\s+(.*?)$/gm, '<blockquote>$1</blockquote>')
942
- .replace(/^(\d+)\.\s+(.*?)$/gm, '<ol start="$1"><li>$2</li></ol>')
943
- .replace(/^[-*]\s+(.*?)$/gm, '<ul><li>$1</li></ul>')
944
- .replace(/\n/g, '<br>');
945
 
946
  currentStreamingMessage.innerHTML += formattedContent;
947
 
@@ -956,20 +348,20 @@ document.addEventListener('DOMContentLoaded', function() {
956
  isStreamingInProgress = false;
957
  currentStreamingMessage = null;
958
  } else {
959
- const messageBox = document.createElement('div');
960
- messageBox.className = `message ${type}`;
961
 
962
- // Apply markdown-like formatting
963
  const formattedContent = content
964
- .replace(/```([\s\S]*?)```/g, (match, code) => `<pre><code>${code}</code></pre>`)
965
- .replace(/(\d+)\.\s*/g, '<br>$1. ')
966
- .replace(/\n/g, '<br>')
967
- .replace(/\*\*(.*?)\*\*/g, (match, p1) => '<strong>' + p1 + '</strong>');
 
968
 
969
  messageBox.innerHTML = formattedContent;
970
  chatMessages.appendChild(messageBox);
971
 
972
- // Add to conversation history only for new messages
973
  if (type === 'user') {
974
  conversationHistory.push({
975
  role: 'user',
@@ -992,6 +384,15 @@ document.addEventListener('DOMContentLoaded', function() {
992
  conversationHistory = [{ role: "system", content: "You are a helpful AI assistant. Assist the user effectively." }];
993
  initialInput.style.display = 'flex';
994
  chatContainer.style.display = 'none';
 
 
 
 
 
 
 
 
 
995
  }
996
 
997
  async function sendInitialMessage() {
@@ -1001,23 +402,29 @@ document.addEventListener('DOMContentLoaded', function() {
1001
  initialInput.style.display = 'none';
1002
  chatContainer.style.display = 'flex';
1003
 
 
 
 
 
 
 
 
 
1004
  appendMessage(userMessage, 'user');
1005
  initialChatInput.value = '';
1006
- scrollToBottom();
1007
 
1008
- // Show the loader, hide the send icon
1009
  sendLoader.style.display = 'block';
1010
- sendButtonIcon.querySelector('svg').style.display = 'none';
1011
 
1012
  try {
1013
  await callApi(userMessage, selectedModel);
1014
  } catch (error) {
1015
  appendMessage("Oops! Something went wrong. Please try again.", 'bot');
1016
- console.error("API Error:", error);
1017
  } finally {
1018
  // Hide loader, show send icon
1019
  sendLoader.style.display = 'none';
1020
- sendButtonIcon.querySelector('svg').style.display = 'block';
1021
  }
1022
  }
1023
 
@@ -1028,19 +435,18 @@ document.addEventListener('DOMContentLoaded', function() {
1028
  appendMessage(userMessage, 'user');
1029
  chatInput.value = '';
1030
 
1031
- // Show the loader, hide the send icon
1032
  sendLoader.style.display = 'block';
1033
- sendButtonIcon.querySelector('svg').style.display = 'none';
1034
 
1035
  try {
1036
  await callApi(userMessage, selectedModel);
1037
  } catch (error) {
1038
  appendMessage("Oops! Something went wrong. Please try again.", 'bot');
1039
- console.error("API Error:", error);
1040
  } finally {
1041
  // Hide loader, show send icon
1042
  sendLoader.style.display = 'none';
1043
- sendButtonIcon.querySelector('svg').style.display = 'block';
1044
  }
1045
  }
1046
 
@@ -1048,10 +454,14 @@ document.addEventListener('DOMContentLoaded', function() {
1048
  let typingIndicator = null;
1049
 
1050
  try {
1051
- // Add typing indicator before any API call
1052
  typingIndicator = document.createElement('div');
1053
- typingIndicator.className = 'message bot typing-indicator';
1054
- typingIndicator.innerHTML = '<span></span><span></span><span></span>';
 
 
 
 
1055
  chatMessages.appendChild(typingIndicator);
1056
  scrollToBottom();
1057
 
@@ -1083,7 +493,6 @@ document.addEventListener('DOMContentLoaded', function() {
1083
  const chunk = decoder.decode(value);
1084
  const cleanedChunk = chunk.trim();
1085
 
1086
- // Handle different API response formats
1087
  if (cleanedChunk.startsWith('data:')) {
1088
  const jsonChunks = cleanedChunk.split("data:").filter(Boolean);
1089
 
@@ -1100,11 +509,10 @@ document.addEventListener('DOMContentLoaded', function() {
1100
  appendMessage(content, 'bot', true);
1101
  }
1102
  } catch (err) {
1103
- console.warn("Parsing error:", err, "for chunk:", jsonString);
1104
  }
1105
  }
1106
  } else {
1107
- // Handle plain text response
1108
  appendMessage(cleanedChunk, 'bot', true);
1109
  }
1110
  }
@@ -1120,14 +528,12 @@ document.addEventListener('DOMContentLoaded', function() {
1120
  stream: true
1121
  };
1122
 
1123
- const headers = {
1124
- "Content-Type": "application/json",
1125
- "Authorization": "playground"
1126
- };
1127
-
1128
  const response = await fetch(url, {
1129
  method: "POST",
1130
- headers: headers,
 
 
 
1131
  body: JSON.stringify(payload)
1132
  });
1133
 
@@ -1151,9 +557,8 @@ document.addEventListener('DOMContentLoaded', function() {
1151
  const chunk = decoder.decode(value);
1152
  buffer += chunk;
1153
 
1154
- // Process complete data chunks
1155
  const lines = buffer.split('\n');
1156
- buffer = lines.pop() || ""; // Keep the last incomplete line in buffer
1157
 
1158
  for (const line of lines) {
1159
  if (line.trim() === '') continue;
@@ -1169,28 +574,11 @@ document.addEventListener('DOMContentLoaded', function() {
1169
  appendMessage(content, 'bot', true);
1170
  }
1171
  } catch (err) {
1172
- console.warn("Parsing error:", err, "for line:", cleanedLine);
1173
  }
1174
  }
1175
  }
1176
  }
1177
-
1178
- // Handle any remaining data in buffer
1179
- if (buffer.trim() !== '') {
1180
- try {
1181
- const cleanedBuffer = buffer.replace(/^data:\s*/, '').trim();
1182
- if (cleanedBuffer !== '[DONE]') {
1183
- const jsonData = JSON.parse(cleanedBuffer);
1184
- const content = jsonData.choices?.[0]?.delta?.content || "";
1185
-
1186
- if (content) {
1187
- appendMessage(content, 'bot', true);
1188
- }
1189
- }
1190
- } catch (err) {
1191
- console.warn("Parsing error for remaining buffer:", err);
1192
- }
1193
- }
1194
  } else {
1195
  throw new Error(`API responded with status ${response.status}`);
1196
  }
@@ -1201,7 +589,6 @@ document.addEventListener('DOMContentLoaded', function() {
1201
 
1202
  } catch (error) {
1203
  console.error("API call error:", error);
1204
- // Remove typing indicator if still present
1205
  if (typingIndicator) {
1206
  typingIndicator.remove();
1207
  }
@@ -1212,25 +599,19 @@ document.addEventListener('DOMContentLoaded', function() {
1212
  // Toggle theme
1213
  function toggleTheme() {
1214
  isDarkMode = !isDarkMode;
1215
- document.body.classList.toggle('light-mode');
1216
 
1217
  // Update icon based on current theme
1218
  if (isDarkMode) {
1219
- themeToggle.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1220
- <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
1221
- </svg>`;
 
1222
  } else {
1223
- themeToggle.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1224
- <circle cx="12" cy="12" r="5"></circle>
1225
- <line x1="12" y1="1" x2="12" y2="3"></line>
1226
- <line x1="12" y1="21" x2="12" y2="23"></line>
1227
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
1228
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
1229
- <line x1="1" y1="12" x2="3" y2="12"></line>
1230
- <line x1="21" y1="12" x2="23" y2="12"></line>
1231
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
1232
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
1233
- </svg>`;
1234
  }
1235
  }
1236
 
@@ -1240,54 +621,71 @@ document.addEventListener('DOMContentLoaded', function() {
1240
  if (event.key === 'Enter') sendInitialMessage();
1241
  });
1242
 
1243
- sendButtonIcon.addEventListener('click', sendMessage);
1244
  chatInput.addEventListener('keypress', (event) => {
1245
  if (event.key === 'Enter') sendMessage();
1246
  });
1247
 
1248
- // Clear Chat Button Event Listener
1249
  clearChatButton.addEventListener('click', clearChat);
1250
 
1251
- // Model selector dropdown event listeners
1252
- modelSelectDisplay.addEventListener('click', function() {
1253
- if (modelOptions.style.display === 'block') {
1254
- modelOptions.style.display = 'none';
1255
- } else {
1256
- modelOptions.style.display = 'block';
1257
- }
1258
  });
1259
 
1260
  // Close dropdown when clicking outside
1261
- document.addEventListener('click', function(event) {
1262
- if (!event.target.closest('.custom-select-wrapper') && modelOptions.style.display === 'block') {
1263
- modelOptions.style.display = 'none';
1264
- }
1265
  });
1266
 
1267
- // Theme toggle event listener
1268
  themeToggle.addEventListener('click', toggleTheme);
1269
 
1270
- // Initialize the app
 
1271
  fetchModels();
1272
-
1273
- // Add an animation to the chat wrapper
1274
- if (typeof anime !== 'undefined') {
1275
- anime({
1276
- targets: '.chat-wrapper',
1277
- translateY: [50, 0],
1278
- opacity: [0, 1],
1279
- easing: 'easeOutExpo',
1280
- duration: 1000
1281
- });
 
 
 
1282
  }
1283
-
1284
- // Detect escape key to close model selector
 
 
 
 
 
 
 
 
 
 
 
 
 
1285
  document.addEventListener('keydown', function(event) {
1286
- if (event.key === 'Escape' && modelOptions.style.display === 'block') {
1287
- modelOptions.style.display = 'none';
 
 
 
 
 
 
1288
  }
1289
  });
1290
  });
1291
- </script>
1292
  </body>
1293
  </html>
 
1
  <!DOCTYPE html>
2
+ <html lang="en" class="dark">
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LOKI.AI Playground</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
10
+ <script>
11
+ tailwind.config = {
12
+ darkMode: 'class',
13
+ theme: {
14
+ extend: {
15
+ fontFamily: {
16
+ mono: ['"JetBrains Mono"', 'monospace'],
17
+ },
18
+ colors: {
19
+ primary: {
20
+ DEFAULT: '#10B981',
21
+ dark: '#059669',
22
+ },
23
+ dark: {
24
+ DEFAULT: '#111827',
25
+ lighter: '#1F2937',
26
+ card: '#1E293B',
27
+ },
28
+ },
29
+ animation: {
30
+ 'bounce-slow': 'bounce 1.5s infinite',
31
+ 'typing': 'typing 1s infinite',
32
+ },
33
+ keyframes: {
34
+ typing: {
35
+ '0%, 100%': { opacity: 0 },
36
+ '50%': { opacity: 1 },
37
+ }
38
+ }
39
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
  }
42
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  </head>
44
 
45
+ <body class="dark:bg-dark dark:text-gray-200 min-h-screen transition-colors duration-300">
46
+ <!-- GitHub Link -->
47
+ <a href="https://github.com/ParthSadaria" target="_blank" rel="noopener noreferrer"
48
+ class="fixed top-4 right-4 text-gray-500 hover:text-primary transition-colors duration-300 z-10"
49
+ aria-label="Visit Parth Sadaria's GitHub profile">
50
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
51
+ class="w-6 h-6" stroke-linecap="round" stroke-linejoin="round">
52
  <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
53
  </svg>
54
  </a>
55
+
56
+ <!-- Chat Container -->
57
+ <div class="max-w-4xl mx-auto py-8 px-4 sm:px-6 opacity-0 transform translate-y-4" id="chatWrapper">
58
+ <!-- Header -->
59
+ <div class="flex justify-between items-center mb-6 p-4 bg-gray-100 dark:bg-dark-lighter rounded-lg shadow-md">
60
+ <h2 class="text-xl font-mono font-bold text-gray-800 dark:text-gray-100">LOKI.AI Playground</h2>
61
+ <div class="flex space-x-2">
62
+ <!-- Model Selector -->
63
+ <div class="relative" id="modelSelector">
64
+ <button class="px-3 py-2 bg-gray-200 dark:bg-dark-card rounded-md text-sm flex items-center space-x-1 hover:bg-gray-300 dark:hover:bg-gray-700 transition-colors" id="modelSelectButton">
65
+ <span id="modelSelectDisplay">Select Model</span>
66
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
67
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
68
+ </svg>
69
+ </button>
70
+ <div class="absolute mt-1 w-56 rounded-md shadow-lg bg-white dark:bg-dark-card ring-1 ring-black ring-opacity-5 hidden max-h-64 overflow-y-auto z-20" id="modelOptions">
71
+ <div class="py-2 px-3 text-sm text-gray-400" id="modelLoader">Loading models...</div>
72
  </div>
73
  </div>
74
+
75
+ <!-- Clear Chat Button -->
76
+ <button id="clearChatButton" class="p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
77
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
78
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
 
 
79
  </svg>
80
  </button>
81
+
82
+ <!-- Theme Toggle Button -->
83
+ <button id="themeToggle" class="p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
84
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
85
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
86
  </svg>
87
  </button>
88
  </div>
89
  </div>
90
 
91
+ <!-- Initial Input -->
92
+ <div class="flex flex-col items-center justify-center space-y-6 py-12" id="initialInput">
93
+ <h2 class="text-2xl font-mono font-semibold">Welcome to LOKI.AI</h2>
94
+ <div class="w-full max-w-2xl relative">
95
+ <input type="text" id="initialChatInput"
96
+ class="w-full p-4 pr-12 rounded-lg border-2 border-gray-300 dark:border-gray-700 bg-white dark:bg-dark-card focus:outline-none focus:ring-2 focus:ring-primary dark:text-white shadow-md transition-all"
97
+ placeholder="What can I help you with today?">
98
+ <button id="initialSendIcon" class="absolute right-3 top-3 text-gray-400 hover:text-primary transition-colors">
99
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
100
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
101
  </svg>
102
+ </button>
103
  </div>
104
  </div>
105
 
106
+ <!-- Chat Area -->
107
+ <div class="hidden flex-col h-[70vh] bg-white dark:bg-dark-card rounded-lg shadow-lg overflow-hidden" id="chatContainer">
108
+ <!-- Messages Area -->
109
+ <div class="flex-1 overflow-y-auto p-4 space-y-4" id="chatMessages"></div>
110
+
111
+ <!-- Input Area -->
112
+ <div class="border-t border-gray-200 dark:border-gray-700 p-4">
113
+ <div class="relative">
114
+ <input type="text" id="chatInput"
115
+ class="w-full p-3 pr-12 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-dark-card focus:outline-none focus:ring-2 focus:ring-primary dark:text-white transition-all"
116
+ placeholder="Type your message...">
117
+ <button id="sendButton" class="absolute right-3 top-3 text-gray-400 hover:text-primary transition-colors">
118
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" id="sendIcon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
119
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
120
  </svg>
121
+ <div id="sendLoader" class="hidden">
122
+ <div class="w-5 h-5 border-2 border-primary border-t-transparent rounded-full animate-spin"></div>
123
+ </div>
124
+ </button>
125
  </div>
126
+ <p class="text-center text-gray-500 text-xs mt-2">Models can make mistakes. Check important info.</p>
127
  </div>
 
 
 
128
  </div>
129
  </div>
130
+
131
+ <!-- Watermark -->
132
+ <div class="fixed bottom-2 right-4 text-xs text-gray-500 font-mono">
133
  Made with ❤️ by Parth Sadaria
134
  </div>
135
+
136
  <script>
137
  document.addEventListener('DOMContentLoaded', function() {
138
+ // DOM Elements
139
+ const chatWrapper = document.getElementById('chatWrapper');
140
+ const initialInput = document.getElementById('initialInput');
141
  const chatContainer = document.getElementById('chatContainer');
142
  const initialChatInput = document.getElementById('initialChatInput');
143
  const initialSendIcon = document.getElementById('initialSendIcon');
144
  const chatMessages = document.getElementById('chatMessages');
145
  const chatInput = document.getElementById('chatInput');
146
+ const sendButton = document.getElementById('sendButton');
147
+ const sendIcon = document.getElementById('sendIcon');
148
  const sendLoader = document.getElementById('sendLoader');
149
  const clearChatButton = document.getElementById('clearChatButton');
150
+ const modelSelectButton = document.getElementById('modelSelectButton');
151
  const modelSelectDisplay = document.getElementById('modelSelectDisplay');
152
  const modelOptions = document.getElementById('modelOptions');
153
  const modelLoader = document.getElementById('modelLoader');
154
  const themeToggle = document.getElementById('themeToggle');
155
 
156
+ // State variables
157
  let currentStreamingMessage = null;
158
  let isStreamingInProgress = false;
159
  let conversationHistory = [{ role: "system", content: "You are a helpful AI assistant. Assist the user effectively." }];
 
161
  let modelsList = [];
162
  let isDarkMode = true;
163
 
164
+ // Initialize with animation
165
+ anime({
166
+ targets: '#chatWrapper',
167
+ opacity: [0, 1],
168
+ translateY: [20, 0],
169
+ easing: 'easeOutExpo',
170
+ duration: 800
171
+ });
172
+
173
  // Fetch models from API
174
  async function fetchModels() {
 
175
  try {
176
  const response = await fetch('https://parthsadaria-lokiai.hf.space/models', {
177
  method: 'GET',
 
185
  modelsList = data;
186
  populateModels(data);
187
  } else {
 
 
188
  const fallbackModels = [
189
  { id: "gpt-4o", object: "model" },
190
  { id: "gpt-3.5-turbo", object: "model" },
 
196
  }
197
  } catch (error) {
198
  console.error('Error fetching models:', error);
 
199
  const fallbackModels = [
200
  { id: "gpt-4o", object: "model" },
201
  { id: "gpt-3.5-turbo", object: "model" },
 
204
  { id: "llama-3.1-70b", object: "model" }
205
  ];
206
  populateModels(fallbackModels);
 
 
207
  }
208
  }
209
 
 
230
  modelOptions.innerHTML = '';
231
 
232
  // Add special option for web search
233
+ const searchOption = document.createElement('div');
234
+ searchOption.className = 'px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer';
235
+ searchOption.textContent = 'SearchGPT (Web-access)';
236
+ searchOption.dataset.value = 'searchgpt';
237
+ modelOptions.appendChild(searchOption);
238
 
239
  // Add a separator
240
  const separator = document.createElement('div');
241
+ separator.className = 'px-4 py-1 text-xs font-semibold text-gray-500 bg-gray-50 dark:bg-gray-800 dark:text-gray-400';
242
  separator.textContent = 'AI Models';
243
  modelOptions.appendChild(separator);
244
 
245
  // Create and append provider groups and their models
246
  Object.keys(providers).sort().forEach(provider => {
247
  const providerGroup = document.createElement('div');
248
+ providerGroup.className = 'px-4 py-1 text-xs font-semibold text-gray-500 bg-gray-50 dark:bg-gray-800 dark:text-gray-400';
249
  providerGroup.textContent = provider;
250
  modelOptions.appendChild(providerGroup);
251
 
252
  providers[provider].sort((a, b) => a.id.localeCompare(b.id)).forEach(model => {
253
  const option = document.createElement('div');
254
+ option.className = 'px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer';
255
  option.textContent = model.id;
256
  option.dataset.value = model.id;
257
  modelOptions.appendChild(option);
 
280
  }
281
 
282
  // Add event listeners to options
283
+ document.querySelectorAll('#modelOptions > div').forEach(option => {
284
+ if (option.dataset.value) {
285
+ option.addEventListener('click', function() {
286
+ selectedModel = this.dataset.value;
287
+ modelSelectDisplay.textContent = this.textContent;
288
+ modelOptions.classList.add('hidden');
289
+ });
290
+ }
291
  });
292
  }
293
 
 
295
  chatMessages.scrollTop = chatMessages.scrollHeight;
296
  }
297
 
298
+ function createMessageElement(type) {
299
+ const messageBox = document.createElement('div');
300
+ messageBox.className = `p-3 rounded-lg ${type === 'user' ? 'bg-primary bg-opacity-10 ml-8' : 'bg-gray-100 dark:bg-gray-800 mr-8'}`;
301
+
302
+ // Add animation
303
+ anime({
304
+ targets: messageBox,
305
+ opacity: [0, 1],
306
+ translateY: [10, 0],
307
+ easing: 'easeOutExpo',
308
+ duration: 400
309
+ });
310
+
311
+ return messageBox;
312
+ }
313
+
314
  function appendMessage(content, type = 'bot', isStreaming = false) {
315
  if (type === 'bot' && isStreaming) {
316
  if (!currentStreamingMessage) {
317
+ currentStreamingMessage = createMessageElement(type);
 
318
  chatMessages.appendChild(currentStreamingMessage);
319
 
320
  // Add to conversation history only at the start of streaming
 
327
  }
328
  }
329
 
330
+ // Format content
331
  const formattedContent = content
332
+ .replace(/```([\s\S]*?)```/g, (match, code) => `<pre class="bg-gray-900 text-gray-100 p-2 rounded my-2 overflow-x-auto">${code}</pre>`)
333
+ .replace(/`([^`]+)`/g, '<code class="bg-gray-200 dark:bg-gray-700 px-1 rounded text-sm">$1</code>')
334
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
335
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
336
+ .replace(/\n/g, '<br>');
 
 
 
 
 
 
 
 
337
 
338
  currentStreamingMessage.innerHTML += formattedContent;
339
 
 
348
  isStreamingInProgress = false;
349
  currentStreamingMessage = null;
350
  } else {
351
+ const messageBox = createMessageElement(type);
 
352
 
353
+ // Format content
354
  const formattedContent = content
355
+ .replace(/```([\s\S]*?)```/g, (match, code) => `<pre class="bg-gray-900 text-gray-100 p-2 rounded my-2 overflow-x-auto">${code}</pre>`)
356
+ .replace(/`([^`]+)`/g, '<code class="bg-gray-200 dark:bg-gray-700 px-1 rounded text-sm">$1</code>')
357
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
358
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
359
+ .replace(/\n/g, '<br>');
360
 
361
  messageBox.innerHTML = formattedContent;
362
  chatMessages.appendChild(messageBox);
363
 
364
+ // Add to conversation history
365
  if (type === 'user') {
366
  conversationHistory.push({
367
  role: 'user',
 
384
  conversationHistory = [{ role: "system", content: "You are a helpful AI assistant. Assist the user effectively." }];
385
  initialInput.style.display = 'flex';
386
  chatContainer.style.display = 'none';
387
+
388
+ // Animation for initial input
389
+ anime({
390
+ targets: '#initialInput',
391
+ opacity: [0, 1],
392
+ translateY: [20, 0],
393
+ easing: 'easeOutExpo',
394
+ duration: 500
395
+ });
396
  }
397
 
398
  async function sendInitialMessage() {
 
402
  initialInput.style.display = 'none';
403
  chatContainer.style.display = 'flex';
404
 
405
+ // Animation for chat container
406
+ anime({
407
+ targets: '#chatContainer',
408
+ opacity: [0, 1],
409
+ easing: 'easeOutExpo',
410
+ duration: 500
411
+ });
412
+
413
  appendMessage(userMessage, 'user');
414
  initialChatInput.value = '';
 
415
 
416
+ // Show loader, hide send icon
417
  sendLoader.style.display = 'block';
418
+ sendIcon.style.display = 'none';
419
 
420
  try {
421
  await callApi(userMessage, selectedModel);
422
  } catch (error) {
423
  appendMessage("Oops! Something went wrong. Please try again.", 'bot');
 
424
  } finally {
425
  // Hide loader, show send icon
426
  sendLoader.style.display = 'none';
427
+ sendIcon.style.display = 'block';
428
  }
429
  }
430
 
 
435
  appendMessage(userMessage, 'user');
436
  chatInput.value = '';
437
 
438
+ // Show loader, hide send icon
439
  sendLoader.style.display = 'block';
440
+ sendIcon.style.display = 'none';
441
 
442
  try {
443
  await callApi(userMessage, selectedModel);
444
  } catch (error) {
445
  appendMessage("Oops! Something went wrong. Please try again.", 'bot');
 
446
  } finally {
447
  // Hide loader, show send icon
448
  sendLoader.style.display = 'none';
449
+ sendIcon.style.display = 'block';
450
  }
451
  }
452
 
 
454
  let typingIndicator = null;
455
 
456
  try {
457
+ // Add typing indicator
458
  typingIndicator = document.createElement('div');
459
+ typingIndicator.className = 'p-3 rounded-lg bg-gray-100 dark:bg-gray-800 mr-8 flex space-x-1';
460
+ typingIndicator.innerHTML = `
461
+ <div class="w-2 h-2 bg-gray-500 rounded-full animate-typing"></div>
462
+ <div class="w-2 h-2 bg-gray-500 rounded-full animate-typing" style="animation-delay: 0.2s"></div>
463
+ <div class="w-2 h-2 bg-gray-500 rounded-full animate-typing" style="animation-delay: 0.4s"></div>
464
+ `;
465
  chatMessages.appendChild(typingIndicator);
466
  scrollToBottom();
467
 
 
493
  const chunk = decoder.decode(value);
494
  const cleanedChunk = chunk.trim();
495
 
 
496
  if (cleanedChunk.startsWith('data:')) {
497
  const jsonChunks = cleanedChunk.split("data:").filter(Boolean);
498
 
 
509
  appendMessage(content, 'bot', true);
510
  }
511
  } catch (err) {
512
+ console.warn("Parsing error:", err);
513
  }
514
  }
515
  } else {
 
516
  appendMessage(cleanedChunk, 'bot', true);
517
  }
518
  }
 
528
  stream: true
529
  };
530
 
 
 
 
 
 
531
  const response = await fetch(url, {
532
  method: "POST",
533
+ headers: {
534
+ "Content-Type": "application/json",
535
+ "Authorization": "playground"
536
+ },
537
  body: JSON.stringify(payload)
538
  });
539
 
 
557
  const chunk = decoder.decode(value);
558
  buffer += chunk;
559
 
 
560
  const lines = buffer.split('\n');
561
+ buffer = lines.pop() || "";
562
 
563
  for (const line of lines) {
564
  if (line.trim() === '') continue;
 
574
  appendMessage(content, 'bot', true);
575
  }
576
  } catch (err) {
577
+ console.warn("Parsing error:", err);
578
  }
579
  }
580
  }
581
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  } else {
583
  throw new Error(`API responded with status ${response.status}`);
584
  }
 
589
 
590
  } catch (error) {
591
  console.error("API call error:", error);
 
592
  if (typingIndicator) {
593
  typingIndicator.remove();
594
  }
 
599
  // Toggle theme
600
  function toggleTheme() {
601
  isDarkMode = !isDarkMode;
602
+ document.documentElement.classList.toggle('dark');
603
 
604
  // Update icon based on current theme
605
  if (isDarkMode) {
606
+ themeToggle.innerHTML = `
607
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
608
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
609
+ </svg>`;
610
  } else {
611
+ themeToggle.innerHTML = `
612
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
613
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
614
+ </svg>`;
 
 
 
 
 
 
 
615
  }
616
  }
617
 
 
621
  if (event.key === 'Enter') sendInitialMessage();
622
  });
623
 
624
+ sendButton.addEventListener('click', sendMessage);
625
  chatInput.addEventListener('keypress', (event) => {
626
  if (event.key === 'Enter') sendMessage();
627
  });
628
 
 
629
  clearChatButton.addEventListener('click', clearChat);
630
 
631
+ // Model selector
632
+ modelSelectButton.addEventListener('click', function(e) {
633
+ e.stopPropagation();
634
+ modelOptions.classList.toggle('hidden');
 
 
 
635
  });
636
 
637
  // Close dropdown when clicking outside
638
+ document.addEventListener('click', function() {
639
+ modelOptions.classList.add('hidden');
 
 
640
  });
641
 
642
+ // Theme toggle
643
  themeToggle.addEventListener('click', toggleTheme);
644
 
645
+ // Initialize
646
+ // Initialize
647
  fetchModels();
648
+
649
+ // Add event listeners to prevent propagation within model options
650
+ modelOptions.addEventListener('click', function(e) {
651
+ e.stopPropagation();
652
+ });
653
+
654
+ // Add responsive behavior
655
+ function resizeChat() {
656
+ if (window.innerWidth < 640) {
657
+ chatContainer.style.height = 'calc(100vh - 180px)';
658
+ } else {
659
+ chatContainer.style.height = '70vh';
660
+ }
661
  }
662
+
663
+ window.addEventListener('resize', resizeChat);
664
+ resizeChat();
665
+
666
+ // Check for saved theme preference
667
+ if (localStorage.getItem('darkMode') === 'false') {
668
+ toggleTheme();
669
+ }
670
+
671
+ // Save theme preference when changed
672
+ themeToggle.addEventListener('click', function() {
673
+ localStorage.setItem('darkMode', isDarkMode);
674
+ });
675
+
676
+ // Keyboard shortcuts
677
  document.addEventListener('keydown', function(event) {
678
+ // Ctrl+Enter to send in chat area
679
+ if (event.ctrlKey && event.key === 'Enter' && document.activeElement === chatInput) {
680
+ sendMessage();
681
+ }
682
+
683
+ // Escape to close model dropdown
684
+ if (event.key === 'Escape') {
685
+ modelOptions.classList.add('hidden');
686
  }
687
  });
688
  });
689
+ </script>
690
  </body>
691
  </html>