vidbye commited on
Commit
d5ca1a0
·
verified ·
1 Parent(s): 6a707b0

Update hf.js

Browse files
Files changed (1) hide show
  1. hf.js +416 -145
hf.js CHANGED
@@ -5,10 +5,10 @@ const axios = require('axios');
5
  const url = require('url');
6
  const app = express();
7
 
8
- // 启用日志
9
  app.use(morgan('dev'));
10
 
11
- // 环境变量配置
12
  const PORT = process.env.HF_PORT || 7860;
13
  const TARGET_URL = process.env.TARGET_URL || 'http://localhost:3010';
14
  const API_PATH = process.env.API_PATH || '/v1';
@@ -20,7 +20,7 @@ console.log(`Service configuration:
20
  - API Path: ${API_PATH}
21
  - Timeout: ${TIMEOUT}ms`);
22
 
23
- // 解析代理设置
24
  let proxyPool = [];
25
  if (process.env.PROXY) {
26
  proxyPool = process.env.PROXY.split(',').map(p => p.trim()).filter(p => p);
@@ -29,14 +29,14 @@ if (process.env.PROXY) {
29
  if (proxyPool.length > 0) {
30
  console.log('Proxy pool initialized:');
31
  proxyPool.forEach((proxy, index) => {
32
- // 隐藏敏感信息的日志
33
  const maskedProxy = proxy.replace(/(https?:\/\/)([^:]+):([^@]+)@/, '$1$2:****@');
34
  console.log(` [${index + 1}] ${maskedProxy}`);
35
  });
36
  }
37
  }
38
 
39
- // 从代理池中随机选择一个代理
40
  function getRandomProxy() {
41
  if (proxyPool.length === 0) return null;
42
  const randomIndex = Math.floor(Math.random() * proxyPool.length);
@@ -53,7 +53,7 @@ function getRandomProxy() {
53
  };
54
  }
55
 
56
- // 模型列表 API
57
  app.get('/hf/v1/models', (req, res) => {
58
  const models = {
59
  "object": "list",
@@ -189,7 +189,7 @@ app.get('/hf/v1/models', (req, res) => {
189
  res.json(models);
190
  });
191
 
192
- // 代理转发,使用动态代理池
193
  app.use('/hf/v1/chat/completions', (req, res, next) => {
194
  const proxy = getRandomProxy();
195
  const targetEndpoint = `${TARGET_URL}${API_PATH}/chat/completions`;
@@ -236,7 +236,7 @@ app.use('/hf/v1/chat/completions', (req, res, next) => {
236
  middleware(req, res, next);
237
  });
238
 
239
- // 首页
240
  app.get('/', (req, res) => {
241
  const htmlContent = `
242
  <!DOCTYPE html>
@@ -245,182 +245,369 @@ app.get('/', (req, res) => {
245
  <meta charset="UTF-8">
246
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
247
  <title>Cursor To OpenAI</title>
 
248
  <style>
249
  :root {
250
- --primary-color: #2563eb;
251
- --bg-color: #f8fafc;
252
- --card-bg: #ffffff;
 
 
 
 
 
 
 
 
 
253
  }
254
- body {
255
- padding: 20px;
 
 
256
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
257
- max-width: 1000px;
258
- margin: 0 auto;
259
- line-height: 1.6;
260
- background: var(--bg-color);
261
- color: #1a1a1a;
262
  }
 
263
  .container {
264
- padding: 20px;
265
- }
266
- .header {
267
- text-align: center;
268
- margin-bottom: 40px;
269
  }
270
- .header h1 {
271
- color: var(--primary-color);
272
- font-size: 2.5em;
273
- margin-bottom: 10px;
 
 
 
 
 
274
  }
275
- .info {
276
- background: #fff;
277
- padding: 25px;
278
- border-radius: 12px;
279
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
280
- margin-bottom: 30px;
281
- border: 1px solid #e5e7eb;
282
  }
 
283
  .info-item {
284
- margin: 15px 0;
285
- padding: 10px;
286
- background: #f8fafc;
287
- border-radius: 8px;
288
- border: 1px solid #e5e7eb;
289
- }
290
- .info-label {
291
- color: #4b5563;
292
- font-size: 0.9em;
293
- margin-bottom: 5px;
294
- }
295
- .info-value {
296
- color: var(--primary-color);
297
- font-weight: 500;
298
  }
 
299
  .service-status {
300
- background: #dcfce7;
301
- border: 1px solid #86efac;
302
- color: #166534;
303
- padding: 8px 12px;
304
- border-radius: 6px;
305
- font-size: 0.95em;
306
  display: inline-block;
307
- margin: 15px 0;
308
- }
309
- .models {
310
- background: var(--card-bg);
311
- padding: 25px;
312
- border-radius: 12px;
313
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
314
- border: 1px solid #e5e7eb;
315
- }
316
- .models h3 {
317
- color: #1a1a1a;
318
- margin-bottom: 20px;
319
- font-size: 1.5em;
320
- border-bottom: 2px solid #e5e7eb;
321
- padding-bottom: 10px;
322
  }
323
- .model-item {
324
- margin: 12px 0;
325
- padding: 15px;
326
- background: #f8fafc;
327
- border-radius: 8px;
328
- border: 1px solid #e5e7eb;
329
- transition: all 0.3s ease;
330
  display: flex;
331
  justify-content: space-between;
332
  align-items: center;
 
333
  }
 
334
  .model-item:hover {
 
335
  transform: translateY(-2px);
336
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
337
  }
338
- .model-name {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  font-weight: 500;
340
- color: #1a1a1a;
 
341
  }
342
- .model-provider {
343
- color: #6b7280;
344
- font-size: 0.9em;
345
- padding: 4px 8px;
346
- background: #f1f5f9;
347
- border-radius: 4px;
348
  }
349
- .details {
350
- margin-top: 20px;
351
- background: #f8fafc;
352
- padding: 15px;
353
- border-radius: 8px;
354
- border: 1px solid #e5e7eb;
355
  }
356
- .details pre {
357
- background: #f1f5f9;
358
- padding: 10px;
359
- border-radius: 6px;
 
 
 
 
 
360
  overflow-x: auto;
 
 
361
  }
362
- @media (max-width: 768px) {
363
- body {
364
- padding: 10px;
365
- }
366
- .container {
367
- padding: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  }
369
  }
370
  </style>
371
  </head>
372
  <body>
373
  <div class="container">
374
- <div class="header">
375
- <h1>Cursor To OpenAI Server</h1>
376
- <p>高性能 AI 模型代理服务</p>
377
- <div class="service-status">服务正在运行</div>
378
  </div>
379
 
380
- <div class="info">
381
- <h2>配置信息</h2>
382
- <div class="info-item">
383
- <div class="info-label">服务环境</div>
384
- <div class="info-value">环境变量配置模式</div>
385
  </div>
386
- <div class="info-item">
387
- <div class="info-label">自定义端点(基本URL)</div>
388
- <div class="info-value" id="endpoint-url"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  </div>
390
- <div class="info-item">
391
- <div class="info-label">目标服务</div>
392
- <div class="info-value">${TARGET_URL}${API_PATH}</div>
 
 
 
 
 
 
393
  </div>
394
- <div class="info-item">
395
- <div class="info-label">代理状态</div>
396
- <div class="info-value">${proxyPool.length > 0 ? `使用中 (${proxyPool.length}个代理)` : '未启用'}</div>
 
 
 
 
397
  </div>
398
  </div>
399
 
400
- <div class="models">
401
- <h3>支持的模型列表</h3>
402
- <div id="model-list"></div>
403
- </div>
404
-
405
- <div class="details">
406
- <h3>使用说明</h3>
407
- <p>在客户端配置以下信息:</p>
408
- <pre>API URL: http://{服务器地址}:${PORT}/hf/v1
409
- API Key: 您的API密钥</pre>
410
- <p>支持兼容OpenAI格式的请求,已预配置转发到${TARGET_URL}${API_PATH}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  </div>
412
  </div>
413
 
414
  <script>
415
- const url = new URL(window.location.href);
416
- const link = url.protocol + '//' + url.host + '/hf/v1';
417
- document.getElementById('endpoint-url').textContent = link;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
 
419
- fetch(link + '/models')
420
- .then(response => response.json())
421
- .then(data => {
 
 
 
 
 
 
422
  const modelList = document.getElementById('model-list');
 
 
 
 
 
 
423
  data.data.forEach(model => {
 
424
  const div = document.createElement('div');
425
  div.className = 'model-item';
426
  div.innerHTML = \`
@@ -428,12 +615,98 @@ API Key: 您的API密钥</pre>
428
  <span class="model-provider">\${model.owned_by}</span>
429
  \`;
430
  modelList.appendChild(div);
 
 
 
 
 
 
431
  });
432
- })
433
- .catch(error => {
434
  console.error('Error fetching models:', error);
435
- document.getElementById('model-list').textContent = '获取模型列表失败';
436
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  </script>
438
  </body>
439
  </html>
@@ -441,7 +714,7 @@ API Key: 您的API密钥</pre>
441
  res.send(htmlContent);
442
  });
443
 
444
- // 健康检查端点
445
  app.get('/health', (req, res) => {
446
  res.status(200).json({
447
  status: 'ok',
@@ -451,11 +724,9 @@ app.get('/health', (req, res) => {
451
  });
452
  });
453
 
454
- // 启动服务
455
  app.listen(PORT, () => {
456
  console.log(`HF Proxy server is running at PORT: ${PORT}`);
457
  console.log(`Target service: ${TARGET_URL}${API_PATH}`);
458
  console.log(`Proxy status: ${proxyPool.length > 0 ? 'Enabled' : 'Disabled'}`);
459
- });
460
-
461
-
 
5
  const url = require('url');
6
  const app = express();
7
 
8
+ // Enable logging
9
  app.use(morgan('dev'));
10
 
11
+ // Environment variables configuration
12
  const PORT = process.env.HF_PORT || 7860;
13
  const TARGET_URL = process.env.TARGET_URL || 'http://localhost:3010';
14
  const API_PATH = process.env.API_PATH || '/v1';
 
20
  - API Path: ${API_PATH}
21
  - Timeout: ${TIMEOUT}ms`);
22
 
23
+ // Parse proxy settings
24
  let proxyPool = [];
25
  if (process.env.PROXY) {
26
  proxyPool = process.env.PROXY.split(',').map(p => p.trim()).filter(p => p);
 
29
  if (proxyPool.length > 0) {
30
  console.log('Proxy pool initialized:');
31
  proxyPool.forEach((proxy, index) => {
32
+ // Log with sensitive information masked
33
  const maskedProxy = proxy.replace(/(https?:\/\/)([^:]+):([^@]+)@/, '$1$2:****@');
34
  console.log(` [${index + 1}] ${maskedProxy}`);
35
  });
36
  }
37
  }
38
 
39
+ // Randomly select a proxy from the pool
40
  function getRandomProxy() {
41
  if (proxyPool.length === 0) return null;
42
  const randomIndex = Math.floor(Math.random() * proxyPool.length);
 
53
  };
54
  }
55
 
56
+ // Models list API
57
  app.get('/hf/v1/models', (req, res) => {
58
  const models = {
59
  "object": "list",
 
189
  res.json(models);
190
  });
191
 
192
+ // Proxy forwarding using dynamic proxy pool
193
  app.use('/hf/v1/chat/completions', (req, res, next) => {
194
  const proxy = getRandomProxy();
195
  const targetEndpoint = `${TARGET_URL}${API_PATH}/chat/completions`;
 
236
  middleware(req, res, next);
237
  });
238
 
239
+ // Homepage with dark themed dashboard
240
  app.get('/', (req, res) => {
241
  const htmlContent = `
242
  <!DOCTYPE html>
 
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;
286
  }
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() {
595
+ try {
596
+ const url = new URL(window.location.href);
597
+ const link = url.protocol + '//' + url.host + '/hf/v1/models';
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 = \`
 
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');
621
+ option.value = model.id;
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>
 
714
  res.send(htmlContent);
715
  });
716
 
717
+ // Health check endpoint
718
  app.get('/health', (req, res) => {
719
  res.status(200).json({
720
  status: 'ok',
 
724
  });
725
  });
726
 
727
+ // Start server
728
  app.listen(PORT, () => {
729
  console.log(`HF Proxy server is running at PORT: ${PORT}`);
730
  console.log(`Target service: ${TARGET_URL}${API_PATH}`);
731
  console.log(`Proxy status: ${proxyPool.length > 0 ? 'Enabled' : 'Disabled'}`);
732
+ });