cursor-ai / hf0.js
vidbye's picture
Rename hf.js to hf0.js
696384c verified
const express = require('express');
const morgan = require('morgan');
const { createProxyMiddleware } = require('http-proxy-middleware');
const axios = require('axios');
const url = require('url');
const app = express();
// Enable logging
app.use(morgan('dev'));
// Environment variables configuration
const PORT = process.env.HF_PORT || 7860;
const TARGET_URL = process.env.TARGET_URL || 'http://localhost:3010';
const API_PATH = process.env.API_PATH || '/v1';
const TIMEOUT = parseInt(process.env.TIMEOUT) || 30000;
console.log(`Service configuration:
- Port: ${PORT}
- Target URL: ${TARGET_URL}
- API Path: ${API_PATH}
- Timeout: ${TIMEOUT}ms`);
// Parse proxy settings
let proxyPool = [];
if (process.env.PROXY) {
proxyPool = process.env.PROXY.split(',').map(p => p.trim()).filter(p => p);
console.log(`Loaded ${proxyPool.length} proxies from environment`);
if (proxyPool.length > 0) {
console.log('Proxy pool initialized:');
proxyPool.forEach((proxy, index) => {
// Log with sensitive information masked
const maskedProxy = proxy.replace(/(https?:\/\/)([^:]+):([^@]+)@/, '$1$2:****@');
console.log(` [${index + 1}] ${maskedProxy}`);
});
}
}
// Randomly select a proxy from the pool
function getRandomProxy() {
if (proxyPool.length === 0) return null;
const randomIndex = Math.floor(Math.random() * proxyPool.length);
const proxyUrl = proxyPool[randomIndex];
const parsedUrl = url.parse(proxyUrl);
return {
host: parsedUrl.hostname,
port: parsedUrl.port || 80,
auth: parsedUrl.auth ? {
username: parsedUrl.auth.split(':')[0],
password: parsedUrl.auth.split(':')[1]
} : undefined
};
}
// Models list API
app.get('/hf/v1/models', (req, res) => {
const models = {
"object": "list",
"data": [
{
"id": "claude-3.5-sonnet",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gpt-4",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gpt-4o",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "claude-3-opus",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gpt-3.5-turbo",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gpt-4-turbo-2024-04-09",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gpt-4o-128k",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gemini-1.5-flash-500k",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "claude-3-haiku-200k",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "claude-3-5-sonnet-200k",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "claude-3-5-sonnet-20241022",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gpt-4o-mini",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "o1-mini",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "o1-preview",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "o1",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "claude-3.5-haiku",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gemini-exp-1206",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gemini-2.0-flash-thinking-exp",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "gemini-2.0-flash-exp",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "deepseek-v3",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "deepseek-r1",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
}
]
};
res.json(models);
});
// Proxy forwarding using dynamic proxy pool
app.use('/hf/v1/chat/completions', (req, res, next) => {
const proxy = getRandomProxy();
const targetEndpoint = `${TARGET_URL}${API_PATH}/chat/completions`;
console.log(`Forwarding request to: ${targetEndpoint}`);
const middleware = createProxyMiddleware({
target: targetEndpoint,
changeOrigin: true,
proxy: proxy ? proxy : undefined,
timeout: TIMEOUT,
proxyTimeout: TIMEOUT,
onProxyReq: (proxyReq, req, res) => {
if (req.body) {
const bodyData = JSON.stringify(req.body);
proxyReq.setHeader('Content-Type', 'application/json');
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
proxyReq.write(bodyData);
proxyReq.end();
}
},
onError: (err, req, res) => {
console.error('Proxy error:', err);
res.status(500).json({
error: {
message: 'Proxy error occurred',
type: 'proxy_error',
details: process.env.NODE_ENV === 'development' ? err.message : undefined
}
});
},
onProxyRes: (proxyRes, req, res) => {
console.log(`Proxy response status: ${proxyRes.statusCode}`);
}
});
if (proxy) {
const maskedProxy = `${proxy.host}:${proxy.port}` + (proxy.auth ? ' (with auth)' : '');
console.log(`Using proxy: ${maskedProxy}`);
} else {
console.log('Direct connection (no proxy)');
}
middleware(req, res, next);
});
// Modern dashboard homepage
app.get('/', (req, res) => {
const htmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Models Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.2.1/css/all.min.css">
<style>
:root {
--primary-color: #5D5CDE;
--primary-dark: #4338ca;
--primary-light: #818cf8;
--secondary-color: #10b981;
--accent-color: #f97316;
--bg-dark: #111827;
--bg-card: #1f2937;
--text-primary: #f3f4f6;
--text-secondary: #d1d5db;
--text-muted: #9ca3af;
--border-color: #374151;
--success-bg: #065f46;
--success-text: #a7f3d0;
--error-bg: #7f1d1d;
--error-text: #fecaca;
--input-bg: #1e293b;
--hover-bg: #2d3748;
--shadow-color: rgba(0, 0, 0, 0.25);
}
body {
background-color: var(--bg-dark);
color: var(--text-primary);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
transition: background-color 0.3s ease, color 0.3s ease;
min-height: 100vh;
}
.dashboard-container {
display: grid;
grid-template-columns: 260px 1fr;
min-height: 100vh;
}
.sidebar {
background-color: var(--bg-card);
border-right: 1px solid var(--border-color);
overflow-y: auto;
transition: transform 0.3s ease;
}
.main-content {
overflow-y: auto;
padding: 1.5rem;
}
.logo {
font-size: 1.5rem;
font-weight: 600;
color: var(--primary-light);
display: flex;
align-items: center;
padding: 1.5rem 1rem;
border-bottom: 1px solid var(--border-color);
}
.logo i {
margin-right: 0.5rem;
color: var(--accent-color);
}
.nav-item {
padding: 0.875rem 1rem;
border-radius: 0.375rem;
margin: 0.25rem 0.5rem;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
color: var(--text-secondary);
}
.nav-item:hover {
background-color: var(--hover-bg);
color: var(--text-primary);
}
.nav-item.active {
background-color: var(--primary-color);
color: white;
}
.nav-item i {
width: 1.25rem;
margin-right: 0.75rem;
text-align: center;
}
.card {
background-color: var(--bg-card);
border-radius: 0.75rem;
border: 1px solid var(--border-color);
box-shadow: 0 4px 6px var(--shadow-color);
margin-bottom: 1.5rem;
padding: 1.5rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 15px var(--shadow-color);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border-color);
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
}
.card-title i {
margin-right: 0.5rem;
color: var(--primary-light);
}
.status-badge {
background-color: var(--success-bg);
color: var(--success-text);
border-radius: 2rem;
padding: 0.25rem 0.75rem;
font-size: 0.875rem;
font-weight: 500;
}
.status-badge.error {
background-color: var(--error-bg);
color: var(--error-text);
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.info-item {
background-color: var(--hover-bg);
border-radius: 0.5rem;
border: 1px solid var(--border-color);
padding: 1rem;
display: flex;
flex-direction: column;
}
.info-label {
color: var(--text-muted);
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
.info-value {
color: var(--primary-light);
font-weight: 500;
word-break: break-all;
}
.model-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
max-height: 400px;
overflow-y: auto;
padding-right: 0.5rem;
}
.model-item {
background-color: var(--hover-bg);
border-radius: 0.5rem;
border: 1px solid var(--border-color);
padding: 1rem;
transition: all 0.2s ease;
position: relative;
}
.model-item:hover {
background-color: var(--bg-dark);
transform: translateY(-2px);
box-shadow: 0 4px 8px var(--shadow-color);
}
.model-name {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 0.5rem;
display: block;
}
.model-provider {
position: absolute;
top: 0.5rem;
right: 0.5rem;
background-color: var(--primary-color);
color: white;
border-radius: 0.25rem;
padding: 0.125rem 0.375rem;
font-size: 0.75rem;
}
.endpoint-box {
background-color: var(--input-bg);
border-radius: 0.5rem;
padding: 1rem;
margin-top: 1rem;
border: 1px solid var(--border-color);
}
.endpoint-url {
font-family: monospace;
background-color: var(--bg-dark);
padding: 0.75rem;
border-radius: 0.25rem;
margin: 0.5rem 0;
overflow-x: auto;
white-space: nowrap;
}
.copy-btn {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 0.25rem;
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s;
}
.copy-btn:hover {
background-color: var(--primary-dark);
}
.chat-container {
display: flex;
flex-direction: column;
height: calc(100vh - 3rem);
}
.chat-header {
padding: 1rem;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-body {
flex-grow: 1;
overflow-y: auto;
padding: 1rem;
}
.message-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.message {
display: flex;
max-width: 80%;
}
.message.user {
align-self: flex-end;
}
.message.bot {
align-self: flex-start;
}
.message-bubble {
padding: 0.75rem 1rem;
border-radius: 1rem;
position: relative;
}
.message.user .message-bubble {
background-color: var(--primary-color);
color: white;
border-bottom-right-radius: 0.25rem;
}
.message.bot .message-bubble {
background-color: var(--hover-bg);
color: var(--text-primary);
border-bottom-left-radius: 0.25rem;
}
.message-time {
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 0.25rem;
text-align: right;
}
.chat-input {
padding: 1rem;
border-top: 1px solid var(--border-color);
}
.input-container {
position: relative;
display: flex;
align-items: center;
}
.message-input {
background-color: var(--input-bg);
border: 1px solid var(--border-color);
color: var(--text-primary);
border-radius: 1.5rem;
padding: 0.875rem 4rem 0.875rem 1rem;
width: 100%;
resize: none;
max-height: 120px;
overflow-y: auto;
font-size: 1rem;
}
.message-input:focus {
outline: none;
border-color: var(--primary-color);
}
.send-btn {
position: absolute;
right: 0.5rem;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 50%;
width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.send-btn:hover {
background-color: var(--primary-dark);
}
.send-btn:disabled {
background-color: var(--border-color);
cursor: not-allowed;
}
.model-select-container {
display: flex;
gap: 0.75rem;
align-items: center;
margin-bottom: 1rem;
}
.model-select {
background-color: var(--input-bg);
border: 1px solid var(--border-color);
color: var(--text-primary);
border-radius: 0.5rem;
padding: 0.5rem;
font-size: 1rem;
flex-grow: 1;
}
.model-label {
color: var(--text-secondary);
font-weight: 500;
white-space: nowrap;
}
.new-chat-btn {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 0.5rem;
padding: 0.5rem 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
transition: all 0.2s;
font-weight: 500;
}
.new-chat-btn:hover {
background-color: var(--primary-dark);
}
.loading-spinner {
display: inline-block;
width: 1.5rem;
height: 1.5rem;
border: 0.25rem solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
margin-right: 0.5rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.system-message {
background-color: var(--hover-bg);
border-radius: 0.5rem;
padding: 0.75rem;
margin-bottom: 1rem;
color: var(--text-muted);
font-style: italic;
font-size: 0.875rem;
display: flex;
align-items: center;
}
.system-message i {
margin-right: 0.5rem;
font-size: 1rem;
}
.connection-status {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--text-secondary);
font-size: 0.875rem;
}
.status-indicator {
width: 0.625rem;
height: 0.625rem;
border-radius: 50%;
background-color: var(--success-bg);
}
.status-indicator.error {
background-color: var(--error-bg);
}
.mobile-menu-btn {
display: none;
background: none;
border: none;
color: var(--text-primary);
font-size: 1.5rem;
cursor: pointer;
padding: 0.5rem;
}
/* Light mode theming */
@media (prefers-color-scheme: light) {
:root {
--bg-dark: #f8fafc;
--bg-card: #ffffff;
--text-primary: #1e293b;
--text-secondary: #475569;
--text-muted: #64748b;
--border-color: #e2e8f0;
--success-bg: #dcfce7;
--success-text: #166534;
--error-bg: #fee2e2;
--error-text: #991b1b;
--input-bg: #f1f5f9;
--hover-bg: #f8fafc;
--shadow-color: rgba(0, 0, 0, 0.1);
}
}
/* Responsive design */
@media (max-width: 1024px) {
.dashboard-container {
grid-template-columns: 220px 1fr;
}
.info-grid, .model-grid {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
}
@media (max-width: 768px) {
.dashboard-container {
grid-template-columns: 1fr;
}
.sidebar {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 260px;
z-index: 10;
transform: translateX(-100%);
}
.sidebar.open {
transform: translateX(0);
}
.mobile-menu-btn {
display: flex;
position: fixed;
top: 1rem;
left: 1rem;
z-index: 20;
background-color: var(--bg-card);
border-radius: 0.5rem;
box-shadow: 0 2px 5px var(--shadow-color);
}
.main-content {
padding-top: 4rem;
}
}
@media (max-width: 640px) {
.info-grid, .model-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<button class="mobile-menu-btn" id="mobile-menu-btn">
<i class="fas fa-bars"></i>
</button>
<div class="dashboard-container">
<aside class="sidebar" id="sidebar">
<div class="logo">
<i class="fas fa-robot"></i>
<span>AI Dashboard</span>
</div>
<nav class="mt-4">
<div class="nav-item active" data-section="dashboard">
<i class="fas fa-chart-line"></i>
<span>Dashboard</span>
</div>
<div class="nav-item" data-section="chat">
<i class="fas fa-comments"></i>
<span>Chat</span>
</div>
<div class="nav-item" data-section="models">
<i class="fas fa-cube"></i>
<span>Models</span>
</div>
<div class="nav-item" data-section="settings">
<i class="fas fa-cog"></i>
<span>Settings</span>
</div>
</nav>
<div class="mt-auto p-4 text-sm text-gray-400">
<div class="connection-status">
<div class="status-indicator" id="connection-indicator"></div>
<span id="connection-status">Connected</span>
</div>
</div>
</aside>
<main class="main-content">
<!-- Dashboard Section -->
<section id="section-dashboard" class="content-section active">
<h1 class="text-2xl font-bold mb-4">Dashboard Overview</h1>
<div class="info-grid">
<div class="card">
<div class="card-header">
<div class="card-title">
<i class="fas fa-server"></i>
<span>API Status</span>
</div>
<div class="status-badge" id="api-status">Active</div>
</div>
<div class="info-item">
<div class="info-label">Target Service</div>
<div class="info-value" id="target-service">${TARGET_URL}${API_PATH}</div>
</div>
<div class="info-item">
<div class="info-label">Proxy Status</div>
<div class="info-value" id="proxy-status">${proxyPool.length > 0 ? `Enabled (${proxyPool.length} proxies)` : 'Disabled'}</div>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i class="fas fa-clock"></i>
<span>Performance</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Request Timeout</div>
<div class="info-value">${TIMEOUT}ms</div>
</div>
<div class="info-item">
<div class="info-label">Service Port</div>
<div class="info-value">${PORT}</div>
</div>
</div>
</div>
<div class="card mt-6">
<div class="card-header">
<div class="card-title">
<i class="fas fa-link"></i>
<span>API Endpoint</span>
</div>
</div>
<p class="text-sm text-gray-400 mb-2">Use this endpoint in your applications to connect to the AI models.</p>
<div class="endpoint-box">
<div class="endpoint-url" id="endpoint-url"></div>
<button class="copy-btn" id="copy-endpoint">
<i class="fas fa-copy mr-2"></i> Copy to clipboard
</button>
</div>
</div>
<div class="card mt-6">
<div class="card-header">
<div class="card-title">
<i class="fas fa-cube"></i>
<span>Popular Models</span>
</div>
<a href="#" class="text-primary-light hover:underline text-sm" data-section="models">View All</a>
</div>
<div id="popular-models" class="model-grid mt-4">
<div class="flex justify-center items-center p-4">
<div class="loading-spinner"></div>
<span class="ml-2">Loading models...</span>
</div>
</div>
</div>
</section>
<!-- Chat Section -->
<section id="section-chat" class="content-section hidden">
<div class="chat-container">
<div class="chat-header">
<div class="model-select-container">
<label class="model-label">Model:</label>
<select id="chat-model-select" class="model-select">
<option value="">Loading models...</option>
</select>
</div>
<button class="new-chat-btn" id="new-chat-btn">
<i class="fas fa-plus"></i>
<span>New Chat</span>
</button>
</div>
<div class="chat-body">
<div class="system-message">
<i class="fas fa-info-circle"></i>
<span>This is a new conversation. Select a model and start chatting!</span>
</div>
<div class="message-list" id="message-list"></div>
</div>
<div class="chat-input">
<div class="input-container">
<textarea
id="message-input"
class="message-input"
placeholder="Type your message here..."
rows="1"></textarea>
<button id="send-message-btn" class="send-btn" disabled>
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
</div>
</section>
<!-- Models Section -->
<section id="section-models" class="content-section hidden">
<h1 class="text-2xl font-bold mb-4">Available Models</h1>
<div class="card">
<div class="card-header">
<div class="card-title">
<i class="fas fa-search"></i>
<span>Model Library</span>
</div>
<input
type="text"
id="model-search"
placeholder="Search models..."
class="px-3 py-1 bg-input-bg text-text-primary border border-border-color rounded-md w-64 text-sm">
</div>
<div id="all-models" class="model-grid mt-4">
<div class="flex justify-center items-center p-4">
<div class="loading-spinner"></div>
<span class="ml-2">Loading models...</span>
</div>
</div>
</div>
</section>
<!-- Settings Section -->
<section id="section-settings" class="content-section hidden">
<h1 class="text-2xl font-bold mb-4">Settings</h1>
<div class="card">
<div class="card-header">
<div class="card-title">
<i class="fas fa-wrench"></i>
<span>Connection Settings</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Target URL</div>
<div class="info-value">${TARGET_URL}</div>
</div>
<div class="info-item">
<div class="info-label">API Path</div>
<div class="info-value">${API_PATH}</div>
</div>
<div class="info-item">
<div class="info-label">Server Port</div>
<div class="info-value">${PORT}</div>
</div>
<div class="info-item">
<div class="info-label">Timeout Setting</div>
<div class="info-value">${TIMEOUT}ms</div>
</div>
</div>
<div class="card mt-6">
<div class="card-header">
<div class="card-title">
<i class="fas fa-shield-alt"></i>
<span>Proxy Configuration</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Proxy Status</div>
<div class="info-value">${proxyPool.length > 0 ? 'Enabled' : 'Disabled'}</div>
</div>
<div class="info-item">
<div class="info-label">Active Proxies</div>
<div class="info-value">${proxyPool.length}</div>
</div>
</div>
</section>
</main>
</div>
<script>
// Store chat history
let chatHistory = [];
let selectedModel = '';
let connectionState = {
connected: true,
status: 'Connected',
};
// Initialize the dashboard
document.addEventListener('DOMContentLoaded', function() {
// Set up mobile menu
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const sidebar = document.getElementById('sidebar');
mobileMenuBtn.addEventListener('click', () => {
sidebar.classList.toggle('open');
});
// Handle navigation
const navItems = document.querySelectorAll('.nav-item');
const sections = document.querySelectorAll('.content-section');
navItems.forEach(item => {
item.addEventListener('click', () => {
const sectionId = item.getAttribute('data-section');
// Update active nav item
navItems.forEach(navItem => navItem.classList.remove('active'));
item.classList.add('active');
// Show selected section
sections.forEach(section => {
section.classList.add('hidden');
section.classList.remove('active');
});
const selectedSection = document.getElementById('section-' + sectionId);
selectedSection.classList.remove('hidden');
selectedSection.classList.add('active');
// Close mobile menu after selection
sidebar.classList.remove('open');
});
});
// Section links
document.querySelectorAll('[data-section]').forEach(link => {
if (!link.classList.contains('nav-item')) {
link.addEventListener('click', (e) => {
e.preventDefault();
const sectionId = link.getAttribute('data-section');
document.querySelector('.nav-item[data-section="' + sectionId + '"]').click();
});
}
});
// Set endpoint URL
const url = new URL(window.location.href);
const endpointUrl = url.protocol + '//' + url.host + '/hf/v1';
document.getElementById('endpoint-url').textContent = endpointUrl;
// Copy endpoint button
document.getElementById('copy-endpoint').addEventListener('click', () => {
navigator.clipboard.writeText(endpointUrl).then(() => {
const btn = document.getElementById('copy-endpoint');
const originalText = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!';
setTimeout(() => {
btn.innerHTML = originalText;
}, 2000);
});
});
// Initialize chat
initChat();
// Fetch and display models
fetchModels();
// Check connection status
checkConnectionStatus();
});
// Initialize chat functionality
function initChat() {
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-message-btn');
const messageList = document.getElementById('message-list');
const modelSelect = document.getElementById('chat-model-select');
const newChatBtn = document.getElementById('new-chat-btn');
// Auto-resize textarea
messageInput.addEventListener('input', () => {
messageInput.style.height = 'auto';
messageInput.style.height = (messageInput.scrollHeight) + 'px';
// Enable/disable send button based on input and model selection
sendBtn.disabled = !messageInput.value.trim() || !modelSelect.value;
});
// Enable/disable send button based on model selection
modelSelect.addEventListener('change', () => {
selectedModel = modelSelect.value;
sendBtn.disabled = !messageInput.value.trim() || !selectedModel;
});
// Send message
sendBtn.addEventListener('click', sendMessage);
// Send message with Enter (but Shift+Enter for new line)
messageInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (!sendBtn.disabled) {
sendMessage();
}
}
});
// New chat button
newChatBtn.addEventListener('click', () => {
chatHistory = [];
messageList.innerHTML = '';
const systemMessage = document.createElement('div');
systemMessage.className = 'system-message';
systemMessage.innerHTML = '<i class="fas fa-info-circle"></i><span>This is a new conversation. Select a model and start chatting!</span>';
messageList.appendChild(systemMessage);
});
}
// Send chat message
async function sendMessage() {
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-message-btn');
const messageList = document.getElementById('message-list');
const modelSelect = document.getElementById('chat-model-select');
// Get message content
const messageText = messageInput.value.trim();
if (!messageText || !modelSelect.value) return;
// Disable input during sending
messageInput.disabled = true;
sendBtn.disabled = true;
// Add user message to chat
addMessageToChat('user', messageText);
// Clear input
messageInput.value = '';
messageInput.style.height = 'auto';
// Prepare payload
const messages = [
...chatHistory.map(msg => ({
role: msg.role,
content: msg.content
})),
{ role: 'user', content: messageText }
];
// Add user message to history
chatHistory.push({
role: 'user',
content: messageText,
timestamp: new Date().toISOString()
});
// Show loading indicator for bot response
const loadingMessageId = 'msg-loading-' + Date.now();
const loadingHTML = \`
<div class="message bot" id="\${loadingMessageId}">
<div class="message-bubble">
<div class="flex items-center">
<div class="loading-spinner" style="width: 1rem; height: 1rem;"></div>
<span class="ml-2">Thinking...</span>
</div>
</div>
</div>
\`;
messageList.insertAdjacentHTML('beforeend', loadingHTML);
messageList.scrollTop = messageList.scrollHeight;
try {
// Send request to API
const url = new URL(window.location.href);
const endpoint = url.protocol + '//' + url.host + '/hf/v1/chat/completions';
const payload = {
model: modelSelect.value,
messages: messages,
temperature: 0.7,
stream: false
};
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
// Remove loading message
const loadingElement = document.getElementById(loadingMessageId);
if (loadingElement) {
loadingElement.remove();
}
if (response.ok) {
const data = await response.json();
if (data.choices && data.choices.length > 0) {
const botResponse = data.choices[0].message.content;
// Add bot message to chat
addMessageToChat('bot', botResponse);
// Add to history
chatHistory.push({
role: 'assistant',
content: botResponse,
timestamp: new Date().toISOString()
});
} else {
// Handle empty response
addSystemMessage('Received an empty response from the model.');
}
} else {
// Handle error response
const errorData = await response.json();
const errorMessage = errorData.error?.message || 'An error occurred while communicating with the API.';
addSystemMessage('Error: ' + errorMessage);
// Update connection status
updateConnectionStatus(false, 'Connection Error');
}
} catch (error) {
// Remove loading message
const loadingElement = document.getElementById(loadingMessageId);
if (loadingElement) {
loadingElement.remove();
}
// Handle error
console.error('Error sending message:', error);
addSystemMessage('Error: ' + (error.message || 'Failed to send message'));
// Update connection status
updateConnectionStatus(false, 'Connection Error');
} finally {
// Re-enable input
messageInput.disabled = false;
sendBtn.disabled = !modelSelect.value;
messageList.scrollTop = messageList.scrollHeight;
// Focus back on input
messageInput.focus();
}
}
// Add message to chat
function addMessageToChat(role, content) {
const messageList = document.getElementById('message-list');
const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const messageHTML = \`
<div class="message \${role}">
<div class="message-bubble">
\${content}
<div class="message-time">\${timestamp}</div>
</div>
</div>
\`;
messageList.insertAdjacentHTML('beforeend', messageHTML);
messageList.scrollTop = messageList.scrollHeight;
}
// Add system message
function addSystemMessage(message) {
const messageList = document.getElementById('message-list');
const systemHTML = \`
<div class="system-message">
<i class="fas fa-exclamation-circle"></i>
<span>\${message}</span>
</div>
\`;
messageList.insertAdjacentHTML('beforeend', systemHTML);
messageList.scrollTop = messageList.scrollHeight;
}
// Fetch models from the API
async function fetchModels() {
try {
const url = new URL(window.location.href);
const link = url.protocol + '//' + url.host + '/hf/v1/models';
const response = await fetch(link);
const data = await response.json();
if (!response.ok) {
throw new Error('Failed to fetch models');
}
const popularModelsContainer = document.getElementById('popular-models');
const allModelsContainer = document.getElementById('all-models');
const modelSelect = document.getElementById('chat-model-select');
// Clear containers
popularModelsContainer.innerHTML = '';
allModelsContainer.innerHTML = '';
modelSelect.innerHTML = '<option value="">Select a model</option>';
// Categorize models
const popularModels = data.data.filter(model =>
model.id.includes('gpt-4') ||
model.id.includes('claude-3') ||
model.id === 'o1' ||
model.id === 'gemini-1.5-flash-500k'
);
// Display popular models (limited to 8)
popularModels.slice(0, 8).forEach(model => {
const modelItem = createModelItem(model);
popularModelsContainer.appendChild(modelItem);
});
// Display all models
data.data.forEach(model => {
const modelItem = createModelItem(model);
allModelsContainer.appendChild(modelItem);
// Add to select dropdown
const option = document.createElement('option');
option.value = model.id;
option.textContent = model.id;
modelSelect.appendChild(option);
});
// Set up model search
const modelSearch = document.getElementById('model-search');
modelSearch.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase().trim();
// Filter models
const modelItems = allModelsContainer.querySelectorAll('.model-item');
modelItems.forEach(item => {
const modelName = item.querySelector('.model-name').textContent.toLowerCase();
if (searchTerm === '' || modelName.includes(searchTerm)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
// Update connection status on successful fetch
updateConnectionStatus(true, 'Connected');
} catch (error) {
console.error('Error fetching models:', error);
// Update UI for error state
const containers = ['popular-models', 'all-models'];
containers.forEach(id => {
const container = document.getElementById(id);
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>';
});
document.getElementById('chat-model-select').innerHTML = '<option value="">Failed to load models</option>';
// Update connection status
updateConnectionStatus(false, 'Connection Error');
}
}
// Create model item element
function createModelItem(model) {
const div = document.createElement('div');
div.className = 'model-item';
div.innerHTML = \`
<span class="model-name">\${model.id}</span>
<span class="model-provider">\${model.owned_by}</span>
\`;
// Click to select model for chat
div.addEventListener('click', () => {
const chatModelSelect = document.getElementById('chat-model-select');
chatModelSelect.value = model.id;
// Trigger change event
const event = new Event('change');
chatModelSelect.dispatchEvent(event);
// Navigate to chat section
document.querySelector('.nav-item[data-section="chat"]').click();
});
return div;
}
// Check and update connection status
async function checkConnectionStatus() {
try {
const url = new URL(window.location.href);
const endpoint = url.protocol + '//' + url.host + '/health';
const response = await fetch(endpoint);
if (response.ok) {
updateConnectionStatus(true, 'Connected');
} else {
updateConnectionStatus(false, 'Connection Error');
}
} catch (error) {
updateConnectionStatus(false, 'Connection Error');
}
// Check again after 30 seconds
setTimeout(checkConnectionStatus, 30000);
}
// Update connection status display
function updateConnectionStatus(connected, status) {
connectionState.connected = connected;
connectionState.status = status;
const indicator = document.getElementById('connection-indicator');
const statusText = document.getElementById('connection-status');
const apiStatus = document.getElementById('api-status');
if (connected) {
indicator.classList.remove('error');
statusText.textContent = status;
if (apiStatus) {
apiStatus.classList.remove('error');
apiStatus.textContent = 'Active';
}
} else {
indicator.classList.add('error');
statusText.textContent = status;
if (apiStatus) {
apiStatus.classList.add('error');
apiStatus.textContent = 'Error';
}
}
}
</script>
</body>
</html>
`;
res.send(htmlContent);
});
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
time: new Date().toISOString(),
proxyCount: proxyPool.length,
target: `${TARGET_URL}${API_PATH}`
});
});
// Start server
app.listen(PORT, () => {
console.log(`HF Proxy server is running at PORT: ${PORT}`);
console.log(`Target service: ${TARGET_URL}${API_PATH}`);
console.log(`Proxy status: ${proxyPool.length > 0 ? 'Enabled' : 'Disabled'}`);
});