vidbye commited on
Commit
97d9de8
·
verified ·
1 Parent(s): c884913

Update hf.js

Browse files
Files changed (1) hide show
  1. hf.js +1026 -276
hf.js CHANGED
@@ -236,7 +236,7 @@ app.use('/hf/v1/chat/completions', (req, res, next) => {
236
  middleware(req, res, next);
237
  });
238
 
239
- // Homepage with dark themed dashboard
240
  app.get('/', (req, res) => {
241
  const htmlContent = `
242
  <!DOCTYPE html>
@@ -244,42 +244,104 @@ app.get('/', (req, res) => {
244
  <head>
245
  <meta charset="UTF-8">
246
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
247
- <title>Cursor To OpenAI</title>
248
  <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
 
249
  <style>
250
  :root {
251
  --primary-color: #5D5CDE;
252
- --secondary-color: #4f46e5;
253
- --accent-color: #818cf8;
254
- --bg-color: #181818;
255
- --card-bg: #242424;
256
- --text-primary: #e2e8f0;
257
- --text-secondary: #94a3b8;
 
 
 
258
  --border-color: #374151;
259
  --success-bg: #065f46;
260
  --success-text: #a7f3d0;
261
- --input-bg: #1e1e1e;
262
- --hover-bg: #2d2d2d;
 
 
 
263
  }
264
 
265
  body {
266
- background-color: var(--bg-color);
267
  color: var(--text-primary);
268
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
269
- transition: all 0.3s ease;
 
270
  }
271
 
272
- .container {
273
- max-width: 1200px;
274
- margin: 0 auto;
 
 
 
 
 
 
 
 
 
 
 
 
275
  padding: 1.5rem;
276
  }
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  .card {
279
- background-color: var(--card-bg);
280
  border-radius: 0.75rem;
281
  border: 1px solid var(--border-color);
282
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
283
  margin-bottom: 1.5rem;
284
  padding: 1.5rem;
285
  transition: transform 0.3s ease, box-shadow 0.3s ease;
@@ -287,308 +349,958 @@ app.get('/', (req, res) => {
287
 
288
  .card:hover {
289
  transform: translateY(-2px);
290
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
291
  }
292
 
293
- .info-item {
294
- background-color: var(--bg-color);
295
- border-radius: 0.5rem;
296
- border: 1px solid var(--border-color);
297
- margin: 0.75rem 0;
298
- padding: 0.75rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  }
300
 
301
- .service-status {
302
  background-color: var(--success-bg);
303
  color: var(--success-text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  border-radius: 0.5rem;
305
- padding: 0.5rem 0.75rem;
306
- display: inline-block;
 
 
 
 
 
 
 
 
 
 
 
 
307
  font-weight: 500;
308
- margin: 1rem 0;
 
 
 
 
 
 
 
 
 
309
  }
310
 
311
  .model-item {
312
- background-color: var(--bg-color);
313
  border-radius: 0.5rem;
314
  border: 1px solid var(--border-color);
315
- padding: 0.75rem 1rem;
316
- margin: 0.5rem 0;
317
- display: flex;
318
- justify-content: space-between;
319
- align-items: center;
320
  transition: all 0.2s ease;
 
321
  }
322
 
323
  .model-item:hover {
324
- background-color: var(--hover-bg);
325
  transform: translateY(-2px);
 
 
 
 
 
 
 
 
326
  }
327
 
328
  .model-provider {
 
 
 
329
  background-color: var(--primary-color);
330
  color: white;
331
  border-radius: 0.25rem;
332
- padding: 0.25rem 0.5rem;
333
- font-size: 0.875rem;
334
  }
335
 
336
- .tab-button {
337
- background-color: var(--card-bg);
338
- color: var(--text-secondary);
 
 
339
  border: 1px solid var(--border-color);
340
- border-bottom: none;
341
- border-radius: 0.5rem 0.5rem 0 0;
342
- padding: 0.75rem 1.5rem;
343
- font-weight: 500;
344
- cursor: pointer;
345
- transition: all 0.2s ease;
346
  }
347
 
348
- .tab-button.active {
 
 
 
 
 
 
 
 
 
 
349
  background-color: var(--primary-color);
350
  color: white;
 
 
 
 
 
 
351
  }
352
 
353
- .tab-content {
354
- display: none;
355
  }
356
 
357
- .tab-content.active {
358
- display: block;
 
 
359
  }
360
 
361
- pre {
362
- background-color: var(--input-bg);
363
- border-radius: 0.5rem;
364
  padding: 1rem;
365
- overflow-x: auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
  color: var(--text-primary);
367
- border: 1px solid var(--border-color);
 
 
 
 
 
 
 
368
  }
369
 
370
- input, textarea, select {
 
 
 
 
 
 
 
 
 
 
 
371
  background-color: var(--input-bg);
372
  border: 1px solid var(--border-color);
373
  color: var(--text-primary);
374
- padding: 0.75rem;
375
- border-radius: 0.5rem;
376
  width: 100%;
 
 
 
377
  font-size: 1rem;
378
  }
379
 
380
- input:focus, textarea:focus, select:focus {
381
- outline: 2px solid var(--primary-color);
382
  border-color: var(--primary-color);
383
  }
384
 
385
- button {
 
 
386
  background-color: var(--primary-color);
387
  color: white;
388
  border: none;
389
- border-radius: 0.5rem;
390
- padding: 0.75rem 1.5rem;
391
- font-weight: 500;
 
 
 
392
  cursor: pointer;
393
- transition: all 0.2s ease;
394
  }
395
 
396
- button:hover {
397
- background-color: var(--secondary-color);
398
  }
399
 
400
- button:disabled {
401
- opacity: 0.6;
402
  cursor: not-allowed;
403
  }
404
 
405
- .response-area {
 
 
 
 
 
 
 
406
  background-color: var(--input-bg);
407
- border-radius: 0.5rem;
408
  border: 1px solid var(--border-color);
409
- padding: 1rem;
410
- min-height: 200px;
411
- max-height: 500px;
412
- overflow-y: auto;
413
- white-space: pre-wrap;
414
- font-family: monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  }
416
 
417
- .loading {
418
  display: inline-block;
419
- width: 20px;
420
- height: 20px;
421
- border: 3px solid rgba(255,255,255,.3);
422
  border-radius: 50%;
423
  border-top-color: white;
424
  animation: spin 1s ease-in-out infinite;
 
425
  }
426
 
427
  @keyframes spin {
428
  to { transform: rotate(360deg); }
429
  }
430
 
431
- .form-group {
432
- margin-bottom: 1.5rem;
 
 
 
 
 
 
 
 
433
  }
434
 
435
- .form-label {
436
- display: block;
437
- margin-bottom: 0.5rem;
 
 
 
 
 
 
438
  color: var(--text-secondary);
 
439
  }
440
 
441
- /* Light/Dark mode toggle support */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  @media (prefers-color-scheme: light) {
443
  :root {
444
- --bg-color: #f8fafc;
445
- --card-bg: #ffffff;
446
- --text-primary: #1a1a1a;
447
- --text-secondary: #4b5563;
448
- --border-color: #e5e7eb;
 
449
  --success-bg: #dcfce7;
450
  --success-text: #166534;
451
- --input-bg: #f8fafc;
452
- --hover-bg: #f1f5f9;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  }
454
  }
455
  </style>
456
  </head>
457
  <body>
458
- <div class="container">
459
- <div class="text-center mt-4 mb-8">
460
- <h1 class="text-4xl font-bold mb-2" style="color: var(--primary-color);">Cursor To OpenAI Server</h1>
461
- <p class="text-xl">High-performance AI Model Proxy Service</p>
462
- <div class="service-status">Service is running</div>
463
- </div>
464
-
465
- <div class="mb-6">
466
- <div class="flex">
467
- <button id="tab-dashboard" class="tab-button active mr-2">Dashboard</button>
468
- <button id="tab-interact" class="tab-button">API Interaction</button>
469
  </div>
470
- </div>
471
-
472
- <div id="content-dashboard" class="tab-content active">
473
- <div class="card">
474
- <h2 class="text-2xl font-bold mb-4">Configuration Information</h2>
475
- <div class="info-item">
476
- <div class="text-sm text-gray-400 mb-1">Service Environment</div>
477
- <div class="font-medium" style="color: var(--primary-color);">Environment Variable Configuration Mode</div>
478
  </div>
479
- <div class="info-item">
480
- <div class="text-sm text-gray-400 mb-1">Custom Endpoint (Base URL)</div>
481
- <div class="font-medium" id="endpoint-url" style="color: var(--primary-color);"></div>
482
  </div>
483
- <div class="info-item">
484
- <div class="text-sm text-gray-400 mb-1">Target Service</div>
485
- <div class="font-medium" id="target-service" style="color: var(--primary-color);">${TARGET_URL}${API_PATH}</div>
486
  </div>
487
- <div class="info-item">
488
- <div class="text-sm text-gray-400 mb-1">Proxy Status</div>
489
- <div class="font-medium" id="proxy-status" style="color: var(--primary-color);">${proxyPool.length > 0 ? `Enabled (${proxyPool.length} proxies)` : 'Disabled'}</div>
490
  </div>
491
- </div>
492
 
493
- <div class="card">
494
- <h2 class="text-2xl font-bold mb-4">Supported Models</h2>
495
- <div id="model-list" class="mt-4">
496
- <div class="flex justify-center">
497
- <div class="loading"></div>
498
- <span class="ml-2">Loading models...</span>
499
- </div>
500
  </div>
501
  </div>
502
-
503
- <div class="card">
504
- <h2 class="text-2xl font-bold mb-4">Usage Instructions</h2>
505
- <p class="mb-3">Configure the following in your client:</p>
506
- <pre id="usage-instructions">API URL: http://{server-address}:${PORT}/hf/v1
507
- API Key: Your API Key</pre>
508
- <p class="mt-3">Supports requests compatible with OpenAI format, pre-configured for forwarding to the target service.</p>
509
- </div>
510
- </div>
511
 
512
- <div id="content-interact" class="tab-content">
513
- <div class="card">
514
- <h2 class="text-2xl font-bold mb-4">Test API Interaction</h2>
515
- <div class="form-group">
516
- <label class="form-label">Model</label>
517
- <select id="model-select" class="text-base">
518
- <option value="">Loading models...</option>
519
- </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  </div>
521
- <div class="form-group">
522
- <label class="form-label">System Message (optional)</label>
523
- <input id="system-message" type="text" placeholder="You are a helpful AI assistant" class="text-base" />
 
 
 
 
 
 
 
 
 
 
 
 
524
  </div>
525
- <div class="form-group">
526
- <label class="form-label">User Message</label>
527
- <textarea id="user-message" rows="4" placeholder="Enter your message here" class="text-base"></textarea>
 
 
 
 
 
 
 
 
 
 
 
 
528
  </div>
529
- <div class="form-group">
530
- <label class="form-label">Temperature (0-2)</label>
531
- <input id="temperature" type="range" min="0" max="2" step="0.1" value="0.7" />
532
- <div class="flex justify-between">
533
- <span>0 (More deterministic)</span>
534
- <span id="temp-value">0.7</span>
535
- <span>2 (More random)</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
  </div>
537
  </div>
538
- <div class="form-group">
539
- <button id="send-button" class="w-full">Send Request</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  </div>
541
- <div class="mt-6">
542
- <h3 class="text-xl font-bold mb-3">Response</h3>
543
- <div id="response-container" class="response-area">No response yet.</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  </div>
545
- </div>
546
- </div>
547
  </div>
548
 
549
  <script>
550
- // Set up UI elements with server config
 
 
 
 
 
 
 
 
551
  document.addEventListener('DOMContentLoaded', function() {
552
- // Handle tabs
553
- const tabButtons = document.querySelectorAll('.tab-button');
554
- const tabContents = document.querySelectorAll('.tab-content');
 
 
 
 
555
 
556
- tabButtons.forEach(button => {
557
- button.addEventListener('click', () => {
558
- const target = button.id.replace('tab-', 'content-');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
 
560
- // Update active tab button
561
- tabButtons.forEach(btn => btn.classList.remove('active'));
562
- button.classList.add('active');
563
 
564
- // Update visible content
565
- tabContents.forEach(content => content.classList.remove('active'));
566
- document.getElementById(target).classList.add('active');
567
  });
568
  });
569
 
570
- // Set up configuration info
 
 
 
 
 
 
 
 
 
 
 
571
  const url = new URL(window.location.href);
572
- const link = url.protocol + '//' + url.host + '/hf/v1';
573
- document.getElementById('endpoint-url').textContent = link;
 
 
 
 
 
 
 
 
 
 
 
 
574
 
575
- // Update usage instructions
576
- document.getElementById('usage-instructions').textContent = \`API URL: \${link}
577
- API Key: Your API Key\`;
578
 
579
  // Fetch and display models
580
  fetchModels();
581
 
582
- // Set up temperature slider
583
- const tempSlider = document.getElementById('temperature');
584
- const tempValue = document.getElementById('temp-value');
585
- tempSlider.addEventListener('input', () => {
586
- tempValue.textContent = tempSlider.value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  });
588
 
589
- // Set up API interaction
590
- document.getElementById('send-button').addEventListener('click', sendAPIRequest);
591
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
 
593
  // Fetch models from the API
594
  async function fetchModels() {
@@ -598,23 +1310,37 @@ API Key: Your API Key\`;
598
  const response = await fetch(link);
599
  const data = await response.json();
600
 
601
- // Populate model list in dashboard
602
- const modelList = document.getElementById('model-list');
603
- modelList.innerHTML = '';
 
 
 
 
604
 
605
- // Populate model select dropdown
606
- const modelSelect = document.getElementById('model-select');
607
- modelSelect.innerHTML = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
 
 
609
  data.data.forEach(model => {
610
- // Add to dashboard list
611
- const div = document.createElement('div');
612
- div.className = 'model-item';
613
- div.innerHTML = \`
614
- <span class="model-name">\${model.id}</span>
615
- <span class="model-provider">\${model.owned_by}</span>
616
- \`;
617
- modelList.appendChild(div);
618
 
619
  // Add to select dropdown
620
  const option = document.createElement('option');
@@ -622,91 +1348,115 @@ API Key: Your API Key\`;
622
  option.textContent = model.id;
623
  modelSelect.appendChild(option);
624
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
  } catch (error) {
626
  console.error('Error fetching models:', error);
627
- document.getElementById('model-list').innerHTML = '<div class="text-red-500">Failed to load models</div>';
628
- document.getElementById('model-select').innerHTML = '<option value="">Failed to load models</option>';
 
 
 
 
 
 
 
 
 
 
629
  }
630
  }
631
 
632
- // Send API request for interaction tab
633
- async function sendAPIRequest() {
634
- const modelSelect = document.getElementById('model-select');
635
- const systemMessage = document.getElementById('system-message');
636
- const userMessage = document.getElementById('user-message');
637
- const temperature = document.getElementById('temperature');
638
- const responseContainer = document.getElementById('response-container');
639
- const sendButton = document.getElementById('send-button');
640
-
641
- // Validate inputs
642
- if (!modelSelect.value) {
643
- responseContainer.textContent = 'Error: Please select a model';
644
- return;
645
- }
646
 
647
- if (!userMessage.value.trim()) {
648
- responseContainer.textContent = 'Error: Please enter a user message';
649
- return;
650
- }
651
-
652
- // Prepare request
653
- const url = new URL(window.location.href);
654
- const endpoint = url.protocol + '//' + url.host + '/hf/v1/chat/completions';
655
-
656
- const payload = {
657
- model: modelSelect.value,
658
- messages: [
659
- ...(systemMessage.value.trim() ? [{ role: 'system', content: systemMessage.value }] : []),
660
- { role: 'user', content: userMessage.value }
661
- ],
662
- temperature: parseFloat(temperature.value),
663
- stream: false
664
- };
665
-
666
- // Show loading state
667
- sendButton.disabled = true;
668
- responseContainer.innerHTML = '<div class="flex justify-center"><div class="loading"></div><span class="ml-2">Waiting for response...</span></div>';
669
 
 
 
 
 
 
670
  try {
671
- const response = await fetch(endpoint, {
672
- method: 'POST',
673
- headers: {
674
- 'Content-Type': 'application/json'
675
- },
676
- body: JSON.stringify(payload)
677
- });
678
-
679
- const data = await response.json();
680
 
 
681
  if (response.ok) {
682
- if (data.choices && data.choices.length > 0) {
683
- const messageContent = data.choices[0].message.content;
684
- responseContainer.textContent = messageContent;
685
- } else {
686
- responseContainer.textContent = 'Received an empty response from the API.';
687
- }
688
  } else {
689
- responseContainer.textContent = \`Error: \${data.error?.message || 'Unknown error'}\`;
690
  }
691
  } catch (error) {
692
- responseContainer.textContent = \`Error: \${error.message || 'Failed to send request'}\`;
693
- } finally {
694
- sendButton.disabled = false;
695
  }
 
 
 
696
  }
697
 
698
- // Light/Dark mode support
699
- if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
700
- document.documentElement.classList.add('dark');
701
- }
702
-
703
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
704
- if (event.matches) {
705
- document.documentElement.classList.add('dark');
 
 
 
 
 
 
 
 
 
706
  } else {
707
- document.documentElement.classList.remove('dark');
 
 
 
 
 
 
708
  }
709
- });
710
  </script>
711
  </body>
712
  </html>
 
236
  middleware(req, res, next);
237
  });
238
 
239
+ // Modern dashboard homepage
240
  app.get('/', (req, res) => {
241
  const htmlContent = `
242
  <!DOCTYPE html>
 
244
  <head>
245
  <meta charset="UTF-8">
246
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
247
+ <title>AI Models Dashboard</title>
248
  <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
249
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.2.1/css/all.min.css">
250
  <style>
251
  :root {
252
  --primary-color: #5D5CDE;
253
+ --primary-dark: #4338ca;
254
+ --primary-light: #818cf8;
255
+ --secondary-color: #10b981;
256
+ --accent-color: #f97316;
257
+ --bg-dark: #111827;
258
+ --bg-card: #1f2937;
259
+ --text-primary: #f3f4f6;
260
+ --text-secondary: #d1d5db;
261
+ --text-muted: #9ca3af;
262
  --border-color: #374151;
263
  --success-bg: #065f46;
264
  --success-text: #a7f3d0;
265
+ --error-bg: #7f1d1d;
266
+ --error-text: #fecaca;
267
+ --input-bg: #1e293b;
268
+ --hover-bg: #2d3748;
269
+ --shadow-color: rgba(0, 0, 0, 0.25);
270
  }
271
 
272
  body {
273
+ background-color: var(--bg-dark);
274
  color: var(--text-primary);
275
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
276
+ transition: background-color 0.3s ease, color 0.3s ease;
277
+ min-height: 100vh;
278
  }
279
 
280
+ .dashboard-container {
281
+ display: grid;
282
+ grid-template-columns: 260px 1fr;
283
+ min-height: 100vh;
284
+ }
285
+
286
+ .sidebar {
287
+ background-color: var(--bg-card);
288
+ border-right: 1px solid var(--border-color);
289
+ overflow-y: auto;
290
+ transition: transform 0.3s ease;
291
+ }
292
+
293
+ .main-content {
294
+ overflow-y: auto;
295
  padding: 1.5rem;
296
  }
297
 
298
+ .logo {
299
+ font-size: 1.5rem;
300
+ font-weight: 600;
301
+ color: var(--primary-light);
302
+ display: flex;
303
+ align-items: center;
304
+ padding: 1.5rem 1rem;
305
+ border-bottom: 1px solid var(--border-color);
306
+ }
307
+
308
+ .logo i {
309
+ margin-right: 0.5rem;
310
+ color: var(--accent-color);
311
+ }
312
+
313
+ .nav-item {
314
+ padding: 0.875rem 1rem;
315
+ border-radius: 0.375rem;
316
+ margin: 0.25rem 0.5rem;
317
+ cursor: pointer;
318
+ transition: all 0.2s ease;
319
+ display: flex;
320
+ align-items: center;
321
+ color: var(--text-secondary);
322
+ }
323
+
324
+ .nav-item:hover {
325
+ background-color: var(--hover-bg);
326
+ color: var(--text-primary);
327
+ }
328
+
329
+ .nav-item.active {
330
+ background-color: var(--primary-color);
331
+ color: white;
332
+ }
333
+
334
+ .nav-item i {
335
+ width: 1.25rem;
336
+ margin-right: 0.75rem;
337
+ text-align: center;
338
+ }
339
+
340
  .card {
341
+ background-color: var(--bg-card);
342
  border-radius: 0.75rem;
343
  border: 1px solid var(--border-color);
344
+ box-shadow: 0 4px 6px var(--shadow-color);
345
  margin-bottom: 1.5rem;
346
  padding: 1.5rem;
347
  transition: transform 0.3s ease, box-shadow 0.3s ease;
 
349
 
350
  .card:hover {
351
  transform: translateY(-2px);
352
+ box-shadow: 0 8px 15px var(--shadow-color);
353
  }
354
 
355
+ .card-header {
356
+ display: flex;
357
+ justify-content: space-between;
358
+ align-items: center;
359
+ margin-bottom: 1rem;
360
+ padding-bottom: 0.75rem;
361
+ border-bottom: 1px solid var(--border-color);
362
+ }
363
+
364
+ .card-title {
365
+ font-size: 1.25rem;
366
+ font-weight: 600;
367
+ color: var(--text-primary);
368
+ display: flex;
369
+ align-items: center;
370
+ }
371
+
372
+ .card-title i {
373
+ margin-right: 0.5rem;
374
+ color: var(--primary-light);
375
  }
376
 
377
+ .status-badge {
378
  background-color: var(--success-bg);
379
  color: var(--success-text);
380
+ border-radius: 2rem;
381
+ padding: 0.25rem 0.75rem;
382
+ font-size: 0.875rem;
383
+ font-weight: 500;
384
+ }
385
+
386
+ .status-badge.error {
387
+ background-color: var(--error-bg);
388
+ color: var(--error-text);
389
+ }
390
+
391
+ .info-grid {
392
+ display: grid;
393
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
394
+ gap: 1rem;
395
+ }
396
+
397
+ .info-item {
398
+ background-color: var(--hover-bg);
399
  border-radius: 0.5rem;
400
+ border: 1px solid var(--border-color);
401
+ padding: 1rem;
402
+ display: flex;
403
+ flex-direction: column;
404
+ }
405
+
406
+ .info-label {
407
+ color: var(--text-muted);
408
+ font-size: 0.875rem;
409
+ margin-bottom: 0.5rem;
410
+ }
411
+
412
+ .info-value {
413
+ color: var(--primary-light);
414
  font-weight: 500;
415
+ word-break: break-all;
416
+ }
417
+
418
+ .model-grid {
419
+ display: grid;
420
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
421
+ gap: 1rem;
422
+ max-height: 400px;
423
+ overflow-y: auto;
424
+ padding-right: 0.5rem;
425
  }
426
 
427
  .model-item {
428
+ background-color: var(--hover-bg);
429
  border-radius: 0.5rem;
430
  border: 1px solid var(--border-color);
431
+ padding: 1rem;
 
 
 
 
432
  transition: all 0.2s ease;
433
+ position: relative;
434
  }
435
 
436
  .model-item:hover {
437
+ background-color: var(--bg-dark);
438
  transform: translateY(-2px);
439
+ box-shadow: 0 4px 8px var(--shadow-color);
440
+ }
441
+
442
+ .model-name {
443
+ font-weight: 600;
444
+ color: var(--text-primary);
445
+ margin-bottom: 0.5rem;
446
+ display: block;
447
  }
448
 
449
  .model-provider {
450
+ position: absolute;
451
+ top: 0.5rem;
452
+ right: 0.5rem;
453
  background-color: var(--primary-color);
454
  color: white;
455
  border-radius: 0.25rem;
456
+ padding: 0.125rem 0.375rem;
457
+ font-size: 0.75rem;
458
  }
459
 
460
+ .endpoint-box {
461
+ background-color: var(--input-bg);
462
+ border-radius: 0.5rem;
463
+ padding: 1rem;
464
+ margin-top: 1rem;
465
  border: 1px solid var(--border-color);
 
 
 
 
 
 
466
  }
467
 
468
+ .endpoint-url {
469
+ font-family: monospace;
470
+ background-color: var(--bg-dark);
471
+ padding: 0.75rem;
472
+ border-radius: 0.25rem;
473
+ margin: 0.5rem 0;
474
+ overflow-x: auto;
475
+ white-space: nowrap;
476
+ }
477
+
478
+ .copy-btn {
479
  background-color: var(--primary-color);
480
  color: white;
481
+ border: none;
482
+ border-radius: 0.25rem;
483
+ padding: 0.375rem 0.75rem;
484
+ font-size: 0.875rem;
485
+ cursor: pointer;
486
+ transition: all 0.2s;
487
  }
488
 
489
+ .copy-btn:hover {
490
+ background-color: var(--primary-dark);
491
  }
492
 
493
+ .chat-container {
494
+ display: flex;
495
+ flex-direction: column;
496
+ height: calc(100vh - 3rem);
497
  }
498
 
499
+ .chat-header {
 
 
500
  padding: 1rem;
501
+ border-bottom: 1px solid var(--border-color);
502
+ display: flex;
503
+ justify-content: space-between;
504
+ align-items: center;
505
+ }
506
+
507
+ .chat-body {
508
+ flex-grow: 1;
509
+ overflow-y: auto;
510
+ padding: 1rem;
511
+ }
512
+
513
+ .message-list {
514
+ display: flex;
515
+ flex-direction: column;
516
+ gap: 1rem;
517
+ }
518
+
519
+ .message {
520
+ display: flex;
521
+ max-width: 80%;
522
+ }
523
+
524
+ .message.user {
525
+ align-self: flex-end;
526
+ }
527
+
528
+ .message.bot {
529
+ align-self: flex-start;
530
+ }
531
+
532
+ .message-bubble {
533
+ padding: 0.75rem 1rem;
534
+ border-radius: 1rem;
535
+ position: relative;
536
+ }
537
+
538
+ .message.user .message-bubble {
539
+ background-color: var(--primary-color);
540
+ color: white;
541
+ border-bottom-right-radius: 0.25rem;
542
+ }
543
+
544
+ .message.bot .message-bubble {
545
+ background-color: var(--hover-bg);
546
  color: var(--text-primary);
547
+ border-bottom-left-radius: 0.25rem;
548
+ }
549
+
550
+ .message-time {
551
+ font-size: 0.75rem;
552
+ color: var(--text-muted);
553
+ margin-top: 0.25rem;
554
+ text-align: right;
555
  }
556
 
557
+ .chat-input {
558
+ padding: 1rem;
559
+ border-top: 1px solid var(--border-color);
560
+ }
561
+
562
+ .input-container {
563
+ position: relative;
564
+ display: flex;
565
+ align-items: center;
566
+ }
567
+
568
+ .message-input {
569
  background-color: var(--input-bg);
570
  border: 1px solid var(--border-color);
571
  color: var(--text-primary);
572
+ border-radius: 1.5rem;
573
+ padding: 0.875rem 4rem 0.875rem 1rem;
574
  width: 100%;
575
+ resize: none;
576
+ max-height: 120px;
577
+ overflow-y: auto;
578
  font-size: 1rem;
579
  }
580
 
581
+ .message-input:focus {
582
+ outline: none;
583
  border-color: var(--primary-color);
584
  }
585
 
586
+ .send-btn {
587
+ position: absolute;
588
+ right: 0.5rem;
589
  background-color: var(--primary-color);
590
  color: white;
591
  border: none;
592
+ border-radius: 50%;
593
+ width: 2.5rem;
594
+ height: 2.5rem;
595
+ display: flex;
596
+ align-items: center;
597
+ justify-content: center;
598
  cursor: pointer;
599
+ transition: all 0.2s;
600
  }
601
 
602
+ .send-btn:hover {
603
+ background-color: var(--primary-dark);
604
  }
605
 
606
+ .send-btn:disabled {
607
+ background-color: var(--border-color);
608
  cursor: not-allowed;
609
  }
610
 
611
+ .model-select-container {
612
+ display: flex;
613
+ gap: 0.75rem;
614
+ align-items: center;
615
+ margin-bottom: 1rem;
616
+ }
617
+
618
+ .model-select {
619
  background-color: var(--input-bg);
 
620
  border: 1px solid var(--border-color);
621
+ color: var(--text-primary);
622
+ border-radius: 0.5rem;
623
+ padding: 0.5rem;
624
+ font-size: 1rem;
625
+ flex-grow: 1;
626
+ }
627
+
628
+ .model-label {
629
+ color: var(--text-secondary);
630
+ font-weight: 500;
631
+ white-space: nowrap;
632
+ }
633
+
634
+ .new-chat-btn {
635
+ background-color: var(--primary-color);
636
+ color: white;
637
+ border: none;
638
+ border-radius: 0.5rem;
639
+ padding: 0.5rem 1rem;
640
+ display: flex;
641
+ align-items: center;
642
+ gap: 0.5rem;
643
+ cursor: pointer;
644
+ transition: all 0.2s;
645
+ font-weight: 500;
646
+ }
647
+
648
+ .new-chat-btn:hover {
649
+ background-color: var(--primary-dark);
650
  }
651
 
652
+ .loading-spinner {
653
  display: inline-block;
654
+ width: 1.5rem;
655
+ height: 1.5rem;
656
+ border: 0.25rem solid rgba(255,255,255,.3);
657
  border-radius: 50%;
658
  border-top-color: white;
659
  animation: spin 1s ease-in-out infinite;
660
+ margin-right: 0.5rem;
661
  }
662
 
663
  @keyframes spin {
664
  to { transform: rotate(360deg); }
665
  }
666
 
667
+ .system-message {
668
+ background-color: var(--hover-bg);
669
+ border-radius: 0.5rem;
670
+ padding: 0.75rem;
671
+ margin-bottom: 1rem;
672
+ color: var(--text-muted);
673
+ font-style: italic;
674
+ font-size: 0.875rem;
675
+ display: flex;
676
+ align-items: center;
677
  }
678
 
679
+ .system-message i {
680
+ margin-right: 0.5rem;
681
+ font-size: 1rem;
682
+ }
683
+
684
+ .connection-status {
685
+ display: flex;
686
+ align-items: center;
687
+ gap: 0.5rem;
688
  color: var(--text-secondary);
689
+ font-size: 0.875rem;
690
  }
691
 
692
+ .status-indicator {
693
+ width: 0.625rem;
694
+ height: 0.625rem;
695
+ border-radius: 50%;
696
+ background-color: var(--success-bg);
697
+ }
698
+
699
+ .status-indicator.error {
700
+ background-color: var(--error-bg);
701
+ }
702
+
703
+ .mobile-menu-btn {
704
+ display: none;
705
+ background: none;
706
+ border: none;
707
+ color: var(--text-primary);
708
+ font-size: 1.5rem;
709
+ cursor: pointer;
710
+ padding: 0.5rem;
711
+ }
712
+
713
+ /* Light mode theming */
714
  @media (prefers-color-scheme: light) {
715
  :root {
716
+ --bg-dark: #f8fafc;
717
+ --bg-card: #ffffff;
718
+ --text-primary: #1e293b;
719
+ --text-secondary: #475569;
720
+ --text-muted: #64748b;
721
+ --border-color: #e2e8f0;
722
  --success-bg: #dcfce7;
723
  --success-text: #166534;
724
+ --error-bg: #fee2e2;
725
+ --error-text: #991b1b;
726
+ --input-bg: #f1f5f9;
727
+ --hover-bg: #f8fafc;
728
+ --shadow-color: rgba(0, 0, 0, 0.1);
729
+ }
730
+ }
731
+
732
+ /* Responsive design */
733
+ @media (max-width: 1024px) {
734
+ .dashboard-container {
735
+ grid-template-columns: 220px 1fr;
736
+ }
737
+
738
+ .info-grid, .model-grid {
739
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
740
+ }
741
+ }
742
+
743
+ @media (max-width: 768px) {
744
+ .dashboard-container {
745
+ grid-template-columns: 1fr;
746
+ }
747
+
748
+ .sidebar {
749
+ position: fixed;
750
+ top: 0;
751
+ left: 0;
752
+ bottom: 0;
753
+ width: 260px;
754
+ z-index: 10;
755
+ transform: translateX(-100%);
756
+ }
757
+
758
+ .sidebar.open {
759
+ transform: translateX(0);
760
+ }
761
+
762
+ .mobile-menu-btn {
763
+ display: flex;
764
+ position: fixed;
765
+ top: 1rem;
766
+ left: 1rem;
767
+ z-index: 20;
768
+ background-color: var(--bg-card);
769
+ border-radius: 0.5rem;
770
+ box-shadow: 0 2px 5px var(--shadow-color);
771
+ }
772
+
773
+ .main-content {
774
+ padding-top: 4rem;
775
+ }
776
+ }
777
+
778
+ @media (max-width: 640px) {
779
+ .info-grid, .model-grid {
780
+ grid-template-columns: 1fr;
781
  }
782
  }
783
  </style>
784
  </head>
785
  <body>
786
+ <button class="mobile-menu-btn" id="mobile-menu-btn">
787
+ <i class="fas fa-bars"></i>
788
+ </button>
789
+
790
+ <div class="dashboard-container">
791
+ <aside class="sidebar" id="sidebar">
792
+ <div class="logo">
793
+ <i class="fas fa-robot"></i>
794
+ <span>AI Dashboard</span>
 
 
795
  </div>
796
+
797
+ <nav class="mt-4">
798
+ <div class="nav-item active" data-section="dashboard">
799
+ <i class="fas fa-chart-line"></i>
800
+ <span>Dashboard</span>
 
 
 
801
  </div>
802
+ <div class="nav-item" data-section="chat">
803
+ <i class="fas fa-comments"></i>
804
+ <span>Chat</span>
805
  </div>
806
+ <div class="nav-item" data-section="models">
807
+ <i class="fas fa-cube"></i>
808
+ <span>Models</span>
809
  </div>
810
+ <div class="nav-item" data-section="settings">
811
+ <i class="fas fa-cog"></i>
812
+ <span>Settings</span>
813
  </div>
814
+ </nav>
815
 
816
+ <div class="mt-auto p-4 text-sm text-gray-400">
817
+ <div class="connection-status">
818
+ <div class="status-indicator" id="connection-indicator"></div>
819
+ <span id="connection-status">Connected</span>
 
 
 
820
  </div>
821
  </div>
822
+ </aside>
 
 
 
 
 
 
 
 
823
 
824
+ <main class="main-content">
825
+ <!-- Dashboard Section -->
826
+ <section id="section-dashboard" class="content-section active">
827
+ <h1 class="text-2xl font-bold mb-4">Dashboard Overview</h1>
828
+
829
+ <div class="info-grid">
830
+ <div class="card">
831
+ <div class="card-header">
832
+ <div class="card-title">
833
+ <i class="fas fa-server"></i>
834
+ <span>API Status</span>
835
+ </div>
836
+ <div class="status-badge" id="api-status">Active</div>
837
+ </div>
838
+ <div class="info-item">
839
+ <div class="info-label">Target Service</div>
840
+ <div class="info-value" id="target-service">${TARGET_URL}${API_PATH}</div>
841
+ </div>
842
+ <div class="info-item">
843
+ <div class="info-label">Proxy Status</div>
844
+ <div class="info-value" id="proxy-status">${proxyPool.length > 0 ? `Enabled (${proxyPool.length} proxies)` : 'Disabled'}</div>
845
+ </div>
846
+ </div>
847
+
848
+ <div class="card">
849
+ <div class="card-header">
850
+ <div class="card-title">
851
+ <i class="fas fa-clock"></i>
852
+ <span>Performance</span>
853
+ </div>
854
+ </div>
855
+ <div class="info-item">
856
+ <div class="info-label">Request Timeout</div>
857
+ <div class="info-value">${TIMEOUT}ms</div>
858
+ </div>
859
+ <div class="info-item">
860
+ <div class="info-label">Service Port</div>
861
+ <div class="info-value">${PORT}</div>
862
+ </div>
863
+ </div>
864
  </div>
865
+
866
+ <div class="card mt-6">
867
+ <div class="card-header">
868
+ <div class="card-title">
869
+ <i class="fas fa-link"></i>
870
+ <span>API Endpoint</span>
871
+ </div>
872
+ </div>
873
+ <p class="text-sm text-gray-400 mb-2">Use this endpoint in your applications to connect to the AI models.</p>
874
+ <div class="endpoint-box">
875
+ <div class="endpoint-url" id="endpoint-url"></div>
876
+ <button class="copy-btn" id="copy-endpoint">
877
+ <i class="fas fa-copy mr-2"></i> Copy to clipboard
878
+ </button>
879
+ </div>
880
  </div>
881
+
882
+ <div class="card mt-6">
883
+ <div class="card-header">
884
+ <div class="card-title">
885
+ <i class="fas fa-cube"></i>
886
+ <span>Popular Models</span>
887
+ </div>
888
+ <a href="#" class="text-primary-light hover:underline text-sm" data-section="models">View All</a>
889
+ </div>
890
+ <div id="popular-models" class="model-grid mt-4">
891
+ <div class="flex justify-center items-center p-4">
892
+ <div class="loading-spinner"></div>
893
+ <span class="ml-2">Loading models...</span>
894
+ </div>
895
+ </div>
896
  </div>
897
+ </section>
898
+
899
+ <!-- Chat Section -->
900
+ <section id="section-chat" class="content-section hidden">
901
+ <div class="chat-container">
902
+ <div class="chat-header">
903
+ <div class="model-select-container">
904
+ <label class="model-label">Model:</label>
905
+ <select id="chat-model-select" class="model-select">
906
+ <option value="">Loading models...</option>
907
+ </select>
908
+ </div>
909
+ <button class="new-chat-btn" id="new-chat-btn">
910
+ <i class="fas fa-plus"></i>
911
+ <span>New Chat</span>
912
+ </button>
913
+ </div>
914
+ <div class="chat-body">
915
+ <div class="system-message">
916
+ <i class="fas fa-info-circle"></i>
917
+ <span>This is a new conversation. Select a model and start chatting!</span>
918
+ </div>
919
+ <div class="message-list" id="message-list"></div>
920
+ </div>
921
+ <div class="chat-input">
922
+ <div class="input-container">
923
+ <textarea
924
+ id="message-input"
925
+ class="message-input"
926
+ placeholder="Type your message here..."
927
+ rows="1"></textarea>
928
+ <button id="send-message-btn" class="send-btn" disabled>
929
+ <i class="fas fa-paper-plane"></i>
930
+ </button>
931
+ </div>
932
+ </div>
933
+ </div>
934
+ </section>
935
+
936
+ <!-- Models Section -->
937
+ <section id="section-models" class="content-section hidden">
938
+ <h1 class="text-2xl font-bold mb-4">Available Models</h1>
939
+
940
+ <div class="card">
941
+ <div class="card-header">
942
+ <div class="card-title">
943
+ <i class="fas fa-search"></i>
944
+ <span>Model Library</span>
945
+ </div>
946
+ <input
947
+ type="text"
948
+ id="model-search"
949
+ placeholder="Search models..."
950
+ class="px-3 py-1 bg-input-bg text-text-primary border border-border-color rounded-md w-64 text-sm">
951
+ </div>
952
+ <div id="all-models" class="model-grid mt-4">
953
+ <div class="flex justify-center items-center p-4">
954
+ <div class="loading-spinner"></div>
955
+ <span class="ml-2">Loading models...</span>
956
+ </div>
957
  </div>
958
  </div>
959
+ </section>
960
+
961
+ <!-- Settings Section -->
962
+ <section id="section-settings" class="content-section hidden">
963
+ <h1 class="text-2xl font-bold mb-4">Settings</h1>
964
+
965
+ <div class="card">
966
+ <div class="card-header">
967
+ <div class="card-title">
968
+ <i class="fas fa-wrench"></i>
969
+ <span>Connection Settings</span>
970
+ </div>
971
+ </div>
972
+ <div class="info-item">
973
+ <div class="info-label">Target URL</div>
974
+ <div class="info-value">${TARGET_URL}</div>
975
+ </div>
976
+ <div class="info-item">
977
+ <div class="info-label">API Path</div>
978
+ <div class="info-value">${API_PATH}</div>
979
+ </div>
980
+ <div class="info-item">
981
+ <div class="info-label">Server Port</div>
982
+ <div class="info-value">${PORT}</div>
983
+ </div>
984
+ <div class="info-item">
985
+ <div class="info-label">Timeout Setting</div>
986
+ <div class="info-value">${TIMEOUT}ms</div>
987
+ </div>
988
  </div>
989
+
990
+ <div class="card mt-6">
991
+ <div class="card-header">
992
+ <div class="card-title">
993
+ <i class="fas fa-shield-alt"></i>
994
+ <span>Proxy Configuration</span>
995
+ </div>
996
+ </div>
997
+ <div class="info-item">
998
+ <div class="info-label">Proxy Status</div>
999
+ <div class="info-value">${proxyPool.length > 0 ? 'Enabled' : 'Disabled'}</div>
1000
+ </div>
1001
+ <div class="info-item">
1002
+ <div class="info-label">Active Proxies</div>
1003
+ <div class="info-value">${proxyPool.length}</div>
1004
+ </div>
1005
  </div>
1006
+ </section>
1007
+ </main>
1008
  </div>
1009
 
1010
  <script>
1011
+ // Store chat history
1012
+ let chatHistory = [];
1013
+ let selectedModel = '';
1014
+ let connectionState = {
1015
+ connected: true,
1016
+ status: 'Connected',
1017
+ };
1018
+
1019
+ // Initialize the dashboard
1020
  document.addEventListener('DOMContentLoaded', function() {
1021
+ // Set up mobile menu
1022
+ const mobileMenuBtn = document.getElementById('mobile-menu-btn');
1023
+ const sidebar = document.getElementById('sidebar');
1024
+
1025
+ mobileMenuBtn.addEventListener('click', () => {
1026
+ sidebar.classList.toggle('open');
1027
+ });
1028
 
1029
+ // Handle navigation
1030
+ const navItems = document.querySelectorAll('.nav-item');
1031
+ const sections = document.querySelectorAll('.content-section');
1032
+
1033
+ navItems.forEach(item => {
1034
+ item.addEventListener('click', () => {
1035
+ const sectionId = item.getAttribute('data-section');
1036
+
1037
+ // Update active nav item
1038
+ navItems.forEach(navItem => navItem.classList.remove('active'));
1039
+ item.classList.add('active');
1040
+
1041
+ // Show selected section
1042
+ sections.forEach(section => {
1043
+ section.classList.add('hidden');
1044
+ section.classList.remove('active');
1045
+ });
1046
 
1047
+ const selectedSection = document.getElementById('section-' + sectionId);
1048
+ selectedSection.classList.remove('hidden');
1049
+ selectedSection.classList.add('active');
1050
 
1051
+ // Close mobile menu after selection
1052
+ sidebar.classList.remove('open');
 
1053
  });
1054
  });
1055
 
1056
+ // Section links
1057
+ document.querySelectorAll('[data-section]').forEach(link => {
1058
+ if (!link.classList.contains('nav-item')) {
1059
+ link.addEventListener('click', (e) => {
1060
+ e.preventDefault();
1061
+ const sectionId = link.getAttribute('data-section');
1062
+ document.querySelector('.nav-item[data-section="' + sectionId + '"]').click();
1063
+ });
1064
+ }
1065
+ });
1066
+
1067
+ // Set endpoint URL
1068
  const url = new URL(window.location.href);
1069
+ const endpointUrl = url.protocol + '//' + url.host + '/hf/v1';
1070
+ document.getElementById('endpoint-url').textContent = endpointUrl;
1071
+
1072
+ // Copy endpoint button
1073
+ document.getElementById('copy-endpoint').addEventListener('click', () => {
1074
+ navigator.clipboard.writeText(endpointUrl).then(() => {
1075
+ const btn = document.getElementById('copy-endpoint');
1076
+ const originalText = btn.innerHTML;
1077
+ btn.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!';
1078
+ setTimeout(() => {
1079
+ btn.innerHTML = originalText;
1080
+ }, 2000);
1081
+ });
1082
+ });
1083
 
1084
+ // Initialize chat
1085
+ initChat();
 
1086
 
1087
  // Fetch and display models
1088
  fetchModels();
1089
 
1090
+ // Check connection status
1091
+ checkConnectionStatus();
1092
+ });
1093
+
1094
+ // Initialize chat functionality
1095
+ function initChat() {
1096
+ const messageInput = document.getElementById('message-input');
1097
+ const sendBtn = document.getElementById('send-message-btn');
1098
+ const messageList = document.getElementById('message-list');
1099
+ const modelSelect = document.getElementById('chat-model-select');
1100
+ const newChatBtn = document.getElementById('new-chat-btn');
1101
+
1102
+ // Auto-resize textarea
1103
+ messageInput.addEventListener('input', () => {
1104
+ messageInput.style.height = 'auto';
1105
+ messageInput.style.height = (messageInput.scrollHeight) + 'px';
1106
+
1107
+ // Enable/disable send button based on input and model selection
1108
+ sendBtn.disabled = !messageInput.value.trim() || !modelSelect.value;
1109
  });
1110
 
1111
+ // Enable/disable send button based on model selection
1112
+ modelSelect.addEventListener('change', () => {
1113
+ selectedModel = modelSelect.value;
1114
+ sendBtn.disabled = !messageInput.value.trim() || !selectedModel;
1115
+ });
1116
+
1117
+ // Send message
1118
+ sendBtn.addEventListener('click', sendMessage);
1119
+
1120
+ // Send message with Enter (but Shift+Enter for new line)
1121
+ messageInput.addEventListener('keydown', (e) => {
1122
+ if (e.key === 'Enter' && !e.shiftKey) {
1123
+ e.preventDefault();
1124
+ if (!sendBtn.disabled) {
1125
+ sendMessage();
1126
+ }
1127
+ }
1128
+ });
1129
+
1130
+ // New chat button
1131
+ newChatBtn.addEventListener('click', () => {
1132
+ chatHistory = [];
1133
+ messageList.innerHTML = '';
1134
+ const systemMessage = document.createElement('div');
1135
+ systemMessage.className = 'system-message';
1136
+ systemMessage.innerHTML = '<i class="fas fa-info-circle"></i><span>This is a new conversation. Select a model and start chatting!</span>';
1137
+ messageList.appendChild(systemMessage);
1138
+ });
1139
+ }
1140
+
1141
+ // Send chat message
1142
+ async function sendMessage() {
1143
+ const messageInput = document.getElementById('message-input');
1144
+ const sendBtn = document.getElementById('send-message-btn');
1145
+ const messageList = document.getElementById('message-list');
1146
+ const modelSelect = document.getElementById('chat-model-select');
1147
+
1148
+ // Get message content
1149
+ const messageText = messageInput.value.trim();
1150
+ if (!messageText || !modelSelect.value) return;
1151
+
1152
+ // Disable input during sending
1153
+ messageInput.disabled = true;
1154
+ sendBtn.disabled = true;
1155
+
1156
+ // Add user message to chat
1157
+ addMessageToChat('user', messageText);
1158
+
1159
+ // Clear input
1160
+ messageInput.value = '';
1161
+ messageInput.style.height = 'auto';
1162
+
1163
+ // Prepare payload
1164
+ const messages = [
1165
+ ...chatHistory.map(msg => ({
1166
+ role: msg.role,
1167
+ content: msg.content
1168
+ })),
1169
+ { role: 'user', content: messageText }
1170
+ ];
1171
+
1172
+ // Add user message to history
1173
+ chatHistory.push({
1174
+ role: 'user',
1175
+ content: messageText,
1176
+ timestamp: new Date().toISOString()
1177
+ });
1178
+
1179
+ // Show loading indicator for bot response
1180
+ const loadingMessageId = 'msg-loading-' + Date.now();
1181
+ const loadingHTML = \`
1182
+ <div class="message bot" id="\${loadingMessageId}">
1183
+ <div class="message-bubble">
1184
+ <div class="flex items-center">
1185
+ <div class="loading-spinner" style="width: 1rem; height: 1rem;"></div>
1186
+ <span class="ml-2">Thinking...</span>
1187
+ </div>
1188
+ </div>
1189
+ </div>
1190
+ \`;
1191
+ messageList.insertAdjacentHTML('beforeend', loadingHTML);
1192
+ messageList.scrollTop = messageList.scrollHeight;
1193
+
1194
+ try {
1195
+ // Send request to API
1196
+ const url = new URL(window.location.href);
1197
+ const endpoint = url.protocol + '//' + url.host + '/hf/v1/chat/completions';
1198
+
1199
+ const payload = {
1200
+ model: modelSelect.value,
1201
+ messages: messages,
1202
+ temperature: 0.7,
1203
+ stream: false
1204
+ };
1205
+
1206
+ const response = await fetch(endpoint, {
1207
+ method: 'POST',
1208
+ headers: {
1209
+ 'Content-Type': 'application/json'
1210
+ },
1211
+ body: JSON.stringify(payload)
1212
+ });
1213
+
1214
+ // Remove loading message
1215
+ const loadingElement = document.getElementById(loadingMessageId);
1216
+ if (loadingElement) {
1217
+ loadingElement.remove();
1218
+ }
1219
+
1220
+ if (response.ok) {
1221
+ const data = await response.json();
1222
+
1223
+ if (data.choices && data.choices.length > 0) {
1224
+ const botResponse = data.choices[0].message.content;
1225
+
1226
+ // Add bot message to chat
1227
+ addMessageToChat('bot', botResponse);
1228
+
1229
+ // Add to history
1230
+ chatHistory.push({
1231
+ role: 'assistant',
1232
+ content: botResponse,
1233
+ timestamp: new Date().toISOString()
1234
+ });
1235
+ } else {
1236
+ // Handle empty response
1237
+ addSystemMessage('Received an empty response from the model.');
1238
+ }
1239
+ } else {
1240
+ // Handle error response
1241
+ const errorData = await response.json();
1242
+ const errorMessage = errorData.error?.message || 'An error occurred while communicating with the API.';
1243
+ addSystemMessage('Error: ' + errorMessage);
1244
+
1245
+ // Update connection status
1246
+ updateConnectionStatus(false, 'Connection Error');
1247
+ }
1248
+ } catch (error) {
1249
+ // Remove loading message
1250
+ const loadingElement = document.getElementById(loadingMessageId);
1251
+ if (loadingElement) {
1252
+ loadingElement.remove();
1253
+ }
1254
+
1255
+ // Handle error
1256
+ console.error('Error sending message:', error);
1257
+ addSystemMessage('Error: ' + (error.message || 'Failed to send message'));
1258
+
1259
+ // Update connection status
1260
+ updateConnectionStatus(false, 'Connection Error');
1261
+ } finally {
1262
+ // Re-enable input
1263
+ messageInput.disabled = false;
1264
+ sendBtn.disabled = !modelSelect.value;
1265
+ messageList.scrollTop = messageList.scrollHeight;
1266
+
1267
+ // Focus back on input
1268
+ messageInput.focus();
1269
+ }
1270
+ }
1271
+
1272
+ // Add message to chat
1273
+ function addMessageToChat(role, content) {
1274
+ const messageList = document.getElementById('message-list');
1275
+ const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1276
+
1277
+ const messageHTML = \`
1278
+ <div class="message \${role}">
1279
+ <div class="message-bubble">
1280
+ \${content}
1281
+ <div class="message-time">\${timestamp}</div>
1282
+ </div>
1283
+ </div>
1284
+ \`;
1285
+
1286
+ messageList.insertAdjacentHTML('beforeend', messageHTML);
1287
+ messageList.scrollTop = messageList.scrollHeight;
1288
+ }
1289
+
1290
+ // Add system message
1291
+ function addSystemMessage(message) {
1292
+ const messageList = document.getElementById('message-list');
1293
+
1294
+ const systemHTML = \`
1295
+ <div class="system-message">
1296
+ <i class="fas fa-exclamation-circle"></i>
1297
+ <span>\${message}</span>
1298
+ </div>
1299
+ \`;
1300
+
1301
+ messageList.insertAdjacentHTML('beforeend', systemHTML);
1302
+ messageList.scrollTop = messageList.scrollHeight;
1303
+ }
1304
 
1305
  // Fetch models from the API
1306
  async function fetchModels() {
 
1310
  const response = await fetch(link);
1311
  const data = await response.json();
1312
 
1313
+ if (!response.ok) {
1314
+ throw new Error('Failed to fetch models');
1315
+ }
1316
+
1317
+ const popularModelsContainer = document.getElementById('popular-models');
1318
+ const allModelsContainer = document.getElementById('all-models');
1319
+ const modelSelect = document.getElementById('chat-model-select');
1320
 
1321
+ // Clear containers
1322
+ popularModelsContainer.innerHTML = '';
1323
+ allModelsContainer.innerHTML = '';
1324
+ modelSelect.innerHTML = '<option value="">Select a model</option>';
1325
+
1326
+ // Categorize models
1327
+ const popularModels = data.data.filter(model =>
1328
+ model.id.includes('gpt-4') ||
1329
+ model.id.includes('claude-3') ||
1330
+ model.id === 'o1' ||
1331
+ model.id === 'gemini-1.5-flash-500k'
1332
+ );
1333
+
1334
+ // Display popular models (limited to 8)
1335
+ popularModels.slice(0, 8).forEach(model => {
1336
+ const modelItem = createModelItem(model);
1337
+ popularModelsContainer.appendChild(modelItem);
1338
+ });
1339
 
1340
+ // Display all models
1341
  data.data.forEach(model => {
1342
+ const modelItem = createModelItem(model);
1343
+ allModelsContainer.appendChild(modelItem);
 
 
 
 
 
 
1344
 
1345
  // Add to select dropdown
1346
  const option = document.createElement('option');
 
1348
  option.textContent = model.id;
1349
  modelSelect.appendChild(option);
1350
  });
1351
+
1352
+ // Set up model search
1353
+ const modelSearch = document.getElementById('model-search');
1354
+ modelSearch.addEventListener('input', (e) => {
1355
+ const searchTerm = e.target.value.toLowerCase().trim();
1356
+
1357
+ // Filter models
1358
+ const modelItems = allModelsContainer.querySelectorAll('.model-item');
1359
+ modelItems.forEach(item => {
1360
+ const modelName = item.querySelector('.model-name').textContent.toLowerCase();
1361
+ if (searchTerm === '' || modelName.includes(searchTerm)) {
1362
+ item.style.display = 'block';
1363
+ } else {
1364
+ item.style.display = 'none';
1365
+ }
1366
+ });
1367
+ });
1368
+
1369
+ // Update connection status on successful fetch
1370
+ updateConnectionStatus(true, 'Connected');
1371
  } catch (error) {
1372
  console.error('Error fetching models:', error);
1373
+
1374
+ // Update UI for error state
1375
+ const containers = ['popular-models', 'all-models'];
1376
+ containers.forEach(id => {
1377
+ const container = document.getElementById(id);
1378
+ container.innerHTML = '<div class="p-4 text-center text-red-400"><i class="fas fa-exclamation-circle mr-2"></i>Failed to load models</div>';
1379
+ });
1380
+
1381
+ document.getElementById('chat-model-select').innerHTML = '<option value="">Failed to load models</option>';
1382
+
1383
+ // Update connection status
1384
+ updateConnectionStatus(false, 'Connection Error');
1385
  }
1386
  }
1387
 
1388
+ // Create model item element
1389
+ function createModelItem(model) {
1390
+ const div = document.createElement('div');
1391
+ div.className = 'model-item';
1392
+ div.innerHTML = \`
1393
+ <span class="model-name">\${model.id}</span>
1394
+ <span class="model-provider">\${model.owned_by}</span>
1395
+ \`;
 
 
 
 
 
 
1396
 
1397
+ // Click to select model for chat
1398
+ div.addEventListener('click', () => {
1399
+ const chatModelSelect = document.getElementById('chat-model-select');
1400
+ chatModelSelect.value = model.id;
1401
+
1402
+ // Trigger change event
1403
+ const event = new Event('change');
1404
+ chatModelSelect.dispatchEvent(event);
1405
+
1406
+ // Navigate to chat section
1407
+ document.querySelector('.nav-item[data-section="chat"]').click();
1408
+ });
 
 
 
 
 
 
 
 
 
 
1409
 
1410
+ return div;
1411
+ }
1412
+
1413
+ // Check and update connection status
1414
+ async function checkConnectionStatus() {
1415
  try {
1416
+ const url = new URL(window.location.href);
1417
+ const endpoint = url.protocol + '//' + url.host + '/health';
 
 
 
 
 
 
 
1418
 
1419
+ const response = await fetch(endpoint);
1420
  if (response.ok) {
1421
+ updateConnectionStatus(true, 'Connected');
 
 
 
 
 
1422
  } else {
1423
+ updateConnectionStatus(false, 'Connection Error');
1424
  }
1425
  } catch (error) {
1426
+ updateConnectionStatus(false, 'Connection Error');
 
 
1427
  }
1428
+
1429
+ // Check again after 30 seconds
1430
+ setTimeout(checkConnectionStatus, 30000);
1431
  }
1432
 
1433
+ // Update connection status display
1434
+ function updateConnectionStatus(connected, status) {
1435
+ connectionState.connected = connected;
1436
+ connectionState.status = status;
1437
+
1438
+ const indicator = document.getElementById('connection-indicator');
1439
+ const statusText = document.getElementById('connection-status');
1440
+ const apiStatus = document.getElementById('api-status');
1441
+
1442
+ if (connected) {
1443
+ indicator.classList.remove('error');
1444
+ statusText.textContent = status;
1445
+
1446
+ if (apiStatus) {
1447
+ apiStatus.classList.remove('error');
1448
+ apiStatus.textContent = 'Active';
1449
+ }
1450
  } else {
1451
+ indicator.classList.add('error');
1452
+ statusText.textContent = status;
1453
+
1454
+ if (apiStatus) {
1455
+ apiStatus.classList.add('error');
1456
+ apiStatus.textContent = 'Error';
1457
+ }
1458
  }
1459
+ }
1460
  </script>
1461
  </body>
1462
  </html>