api / hf.js
ASONGHP's picture
Update hf.js
22dfc7d verified
raw
history blame
37.8 kB
const express = require('express');
const morgan = require('morgan');
const { createProxyMiddleware } = require('http-proxy-middleware');
const url = require('url');
const app = express();
app.use(morgan('dev'));
// Get proxy configuration from environment variables
const proxyUrl = process.env.PROXY || '';
console.log(`Proxy configuration: ${proxyUrl ? 'Configured' : 'Not configured'}`);
console.log(`Raw proxy URL: ${proxyUrl}`); // Print the raw proxy URL
// Parse proxy URL
let proxyConfig = null;
if (proxyUrl) {
try {
const parsedUrl = url.parse(proxyUrl);
proxyConfig = {
host: parsedUrl.hostname,
port: parsedUrl.port || 80,
auth: parsedUrl.auth ? {
username: parsedUrl.auth.split(':')[0],
password: parsedUrl.auth.split(':')[1]
} : undefined
};
// Print EXACT proxy configuration with actual username and password
console.log('Using proxy with EXACT credentials:', JSON.stringify(proxyConfig));
if (proxyConfig.auth) {
console.log('EXACT AUTH DETAILS:');
console.log('Username:', proxyConfig.auth.username);
console.log('Password:', proxyConfig.auth.password);
}
} catch (error) {
console.error('Failed to parse proxy URL:', error.message);
}
}
// Add 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"
},
// New models
{
"id": "claude-3.7-sonnet",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
},
{
"id": "claude-3.7-sonnet-thinking",
"object": "model",
"created": 1706745938,
"owned_by": "cursor"
}
]
};
res.json(models);
});
// Configure proxy middleware
app.use('/hf/v1/chat/completions', createProxyMiddleware({
target: 'http://localhost:3010/v1/chat/completions',
changeOrigin: true,
// Add proxy configuration
proxy: proxyConfig,
// Add error handling
onError: (err, req, res) => {
console.error('Proxy error:', err);
res.status(500).send('Proxy error occurred: ' + err.message);
},
onProxyReq: (proxyReq, req, res) => {
console.log(`Proxying request to chat completions ${proxyConfig ? 'using proxy' : 'directly'}`);
},
onProxyRes: (proxyRes, req, res) => {
console.log(`Received response with status: ${proxyRes.statusCode}`);
}
}));
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>Cursor To OpenAI</title>
<style>
/* Core Variables */
:root {
--bg-primary: #ffffff;
--bg-secondary: #f5f7fa;
--bg-tertiary: #edf0f7;
--text-primary: #2c3e50;
--text-secondary: #596877;
--border-color: #dce1e8;
--accent-color: #5D5CDE;
--accent-hover: #4a49c8;
--success-bg: #e1f5e1;
--success-border: #c3e6cb;
--warning-bg: #fff3cd;
--warning-border: #ffeeba;
--card-shadow: 0 2px 5px rgba(0,0,0,0.05);
--hover-shadow: 0 5px 15px rgba(0,0,0,0.08);
--transition: all 0.2s ease-in-out;
}
[data-theme="dark"] {
--bg-primary: #121821;
--bg-secondary: #1a2332;
--bg-tertiary: #243044;
--text-primary: #e0e6ed;
--text-secondary: #9ba9b9;
--border-color: #324156;
--accent-color: #7D7CED;
--accent-hover: #9795f0;
--success-bg: #1e3a1e;
--success-border: #2a5a2a;
--warning-bg: #3a3018;
--warning-border: #5a4820;
--card-shadow: 0 2px 5px rgba(0,0,0,0.2);
--hover-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
/* Reset & Base Styles */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.5;
transition: var(--transition);
padding: 0;
overflow-x: hidden;
max-width: 100vw;
}
/* Typography */
h1, h2, h3, h4 {
margin: 0;
font-weight: 600;
line-height: 1.2;
}
h1 { font-size: 1.5rem; }
h2 { font-size: 1.25rem; }
h3 { font-size: 1.1rem; }
h4 { font-size: 1rem; }
/* Layout */
.dashboard {
display: grid;
grid-template-columns: 250px 1fr;
min-height: 100vh;
}
/* Sidebar */
.sidebar {
background: var(--bg-secondary);
padding: 1rem;
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
}
.logo {
display: flex;
align-items: center;
gap: 0.5rem;
padding-bottom: 1rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
.nav-item {
display: flex;
align-items: center;
padding: 0.5rem 0.75rem;
margin-bottom: 0.5rem;
border-radius: 6px;
cursor: pointer;
transition: var(--transition);
color: var(--text-secondary);
}
.nav-item:hover, .nav-item.active {
background: var(--bg-tertiary);
color: var(--accent-color);
}
.nav-item i {
margin-right: 8px;
width: 20px;
text-align: center;
}
/* Main Content */
.main-content {
padding: 1rem;
overflow-y: auto;
height: 100vh;
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.stat-card {
background: var(--bg-secondary);
border-radius: 8px;
padding: 1rem;
box-shadow: var(--card-shadow);
display: flex;
flex-direction: column;
}
.stat-card .label {
color: var(--text-secondary);
font-size: 0.875rem;
margin-bottom: 0.25rem;
}
.stat-card .value {
font-size: 1.25rem;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Tabs */
.tabs-container {
margin-top: 1rem;
}
.tabs-header {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 1rem;
}
.tab-button {
padding: 0.75rem 1rem;
background: none;
border: none;
border-bottom: 2px solid transparent;
color: var(--text-secondary);
cursor: pointer;
font-weight: 500;
transition: var(--transition);
}
.tab-button.active {
color: var(--accent-color);
border-bottom-color: var(--accent-color);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* Models Wall */
.models-container {
margin-top: 1rem;
}
.models-grid {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
max-height: 150px;
overflow-y: auto;
padding: 0.5rem;
background: var(--bg-tertiary);
border-radius: 8px;
}
.model-tag {
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 15px;
padding: 0.25rem 0.75rem;
font-size: 0.8rem;
white-space: nowrap;
transition: var(--transition);
}
.model-tag:hover {
background: var(--accent-color);
color: white;
border-color: var(--accent-color);
transform: translateY(-2px);
box-shadow: var(--hover-shadow);
}
/* Setup Guide */
.guide-container {
padding: 1rem;
background: var(--bg-secondary);
border-radius: 8px;
}
.guide-steps {
display: flex;
margin-top: 1rem;
overflow-x: auto;
gap: 1rem;
padding-bottom: 0.5rem;
}
.guide-step {
min-width: 250px;
padding: 1rem;
background: var(--bg-tertiary);
border-radius: 8px;
border-left: 3px solid var(--accent-color);
flex: 1;
}
.guide-step h4 {
margin-bottom: 0.5rem;
display: flex;
align-items: center;
}
.guide-step h4 .step-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
background: var(--accent-color);
color: white;
border-radius: 50%;
margin-right: 0.5rem;
font-size: 0.8rem;
}
.code-example {
margin-top: 0.75rem;
background: var(--bg-primary);
padding: 0.75rem;
border-radius: 6px;
font-family: monospace;
font-size: 0.8rem;
overflow-x: auto;
position: relative;
}
.copy-btn {
position: absolute;
top: 5px;
right: 5px;
background: var(--bg-secondary);
border: none;
border-radius: 4px;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0.7;
transition: var(--transition);
}
.copy-btn:hover {
opacity: 1;
background: var(--accent-color);
color: white;
}
/* Theme Toggle */
.theme-toggle {
margin-top: auto;
padding-top: 1rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 46px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--bg-tertiary);
transition: var(--transition);
border-radius: 24px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: var(--accent-color);
transition: var(--transition);
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: var(--bg-tertiary);
}
input:checked + .toggle-slider:before {
transform: translateX(22px);
}
/* Responsive Layout */
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
}
.sidebar {
display: none;
}
.sidebar.active {
display: flex;
position: fixed;
width: 250px;
height: 100vh;
z-index: 1000;
}
.mobile-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: var(--bg-secondary);
box-shadow: var(--card-shadow);
}
.menu-toggle {
display: block;
background: none;
border: none;
color: var(--text-primary);
cursor: pointer;
font-size: 1.5rem;
}
}
@media (min-width: 769px) {
.mobile-header {
display: none;
}
}
</style>
</head>
<body>
<div class="dashboard">
<!-- Mobile Header (shows only on mobile) -->
<div class="mobile-header">
<button class="menu-toggle" id="menu-toggle">☰</button>
<h1>Cursor API</h1>
<div></div> <!-- Empty div for flexbox spacing -->
</div>
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="logo">
<h1>Cursor API</h1>
</div>
<div class="nav-item active" data-tab="dashboard">
<i>📊</i> Dashboard
</div>
<div class="nav-item" data-tab="models">
<i>🤖</i> Models
</div>
<div class="nav-item" data-tab="guide">
<i>📚</i> Integration Guide
</div>
<div class="nav-item" data-tab="tester">
<i>🧪</i> API Tester
</div>
<!-- Theme Toggle -->
<div class="theme-toggle">
<span>Dark Mode</span>
<label class="toggle-switch">
<input type="checkbox" id="theme-toggle">
<span class="toggle-slider"></span>
</label>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="label">API Endpoint</div>
<div class="value" id="endpoint-url">Loading...</div>
</div>
<div class="stat-card">
<div class="label">Authentication</div>
<div class="value">Cursor Cookie (user_...)</div>
</div>
<div class="stat-card">
<div class="label">Proxy Status</div>
<div class="value">${proxyConfig ? 'Enabled' : 'Disabled'}</div>
</div>
<div class="stat-card">
<div class="label">Available Models</div>
<div class="value" id="model-count">Loading...</div>
</div>
</div>
<!-- Tabs Container -->
<div class="tabs-container">
<div class="tabs-header">
<button class="tab-button active" data-tab="dashboard">Dashboard</button>
<button class="tab-button" data-tab="models">Models</button>
<button class="tab-button" data-tab="guide">Integration Guide</button>
<button class="tab-button" data-tab="tester">API Tester</button>
</div>
<!-- Dashboard Tab -->
<div class="tab-content active" id="dashboard-tab">
<h2>Server Information</h2>
<div class="models-container">
<h3>Featured Models</h3>
<div class="models-grid" id="featured-models">
Loading...
</div>
</div>
<!-- Connection Guide Summary -->
<div class="guide-container">
<h3>Quick Start Guide</h3>
<div class="guide-steps">
<div class="guide-step">
<h4><span class="step-number">1</span>Authentication</h4>
<p>Get your Cursor cookie that starts with "user_..." from browser cookies after logging in to Cursor.</p>
</div>
<div class="guide-step">
<h4><span class="step-number">2</span>API Requests</h4>
<p>Send POST requests to <span id="endpoint-shorthand">Loading...</span> with your Cursor cookie as Bearer token.</p>
</div>
<div class="guide-step">
<h4><span class="step-number">3</span>Request Format</h4>
<p>Use OpenAI-compatible format with model, messages array, and optional parameters.</p>
</div>
</div>
</div>
</div>
<!-- Models Tab -->
<div class="tab-content" id="models-tab">
<h2>Available Models</h2>
<div class="models-grid" id="all-models">
Loading...
</div>
<div class="models-container">
<h3>Model Categories</h3>
<div class="stats-grid">
<div class="stat-card">
<div class="label">Claude Models</div>
<div class="value" id="claude-count">-</div>
</div>
<div class="stat-card">
<div class="label">GPT Models</div>
<div class="value" id="gpt-count">-</div>
</div>
<div class="stat-card">
<div class="label">Gemini Models</div>
<div class="value" id="gemini-count">-</div>
</div>
<div class="stat-card">
<div class="label">Other Models</div>
<div class="value" id="other-count">-</div>
</div>
</div>
</div>
</div>
<!-- Integration Guide Tab -->
<div class="tab-content" id="guide-tab">
<h2>Integration Guide</h2>
<div class="guide-steps">
<div class="guide-step">
<h4><span class="step-number">1</span>Authentication</h4>
<p>To authenticate with the Cursor API:</p>
<ol style="margin-left: 1rem; margin-top: 0.5rem;">
<li>Log in to <a href="https://cursor.so" target="_blank">Cursor.so</a></li>
<li>Open Developer Tools (F12)</li>
<li>Go to Application → Cookies</li>
<li>Find cookie with name starting with "user_"</li>
<li>Use this value as your API key</li>
</ol>
</div>
<div class="guide-step">
<h4><span class="step-number">2</span>API Configuration</h4>
<p>Set up your API client with:</p>
<ul style="margin-left: 1rem; margin-top: 0.5rem;">
<li>Base URL: <span id="endpoint-guide">Loading...</span></li>
<li>Headers:</li>
<div class="code-example">
Content-Type: application/json
Authorization: Bearer user_your_cookie_value
</div>
</ul>
</div>
<div class="guide-step">
<h4><span class="step-number">3</span>Making Requests</h4>
<p>Send chat completion requests:</p>
<div class="code-example">
POST /chat/completions
{
"model": "claude-3.7-sonnet",
"messages": [
{"role": "user", "content": "Hello!"}
],
"temperature": 0.7
}
<button class="copy-btn" title="Copy to clipboard">📋</button>
</div>
</div>
</div>
<div class="guide-container">
<h3>Code Examples</h3>
<div class="tabs-header" style="margin-top: 0.5rem;">
<button class="tab-button active" data-code-tab="js">JavaScript</button>
<button class="tab-button" data-code-tab="python">Python</button>
<button class="tab-button" data-code-tab="curl">cURL</button>
</div>
<div class="code-tab active" id="js-code">
<div class="code-example">
const response = await fetch('${req.protocol}://${req.get('host')}/hf/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer user_your_cookie_value'
},
body: JSON.stringify({
model: 'claude-3.7-sonnet',
messages: [
{ role: 'user', content: 'Hello, who are you?' }
],
temperature: 0.7
})
});
const data = await response.json();
console.log(data);
<button class="copy-btn" title="Copy to clipboard">📋</button>
</div>
</div>
<div class="code-tab" id="python-code">
<div class="code-example">
import requests
url = "${req.protocol}://${req.get('host')}/hf/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer user_your_cookie_value"
}
payload = {
"model": "claude-3.7-sonnet",
"messages": [
{"role": "user", "content": "Hello, who are you?"}
],
"temperature": 0.7
}
response = requests.post(url, headers=headers, json=payload)
data = response.json()
print(data)
<button class="copy-btn" title="Copy to clipboard">📋</button>
</div>
</div>
<div class="code-tab" id="curl-code">
<div class="code-example">
curl -X POST "${req.protocol}://${req.get('host')}/hf/v1/chat/completions" \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer user_your_cookie_value" \\
-d '{
"model": "claude-3.7-sonnet",
"messages": [
{"role": "user", "content": "Hello, who are you?"}
],
"temperature": 0.7
}'
<button class="copy-btn" title="Copy to clipboard">📋</button>
</div>
</div>
</div>
</div>
<!-- API Tester Tab -->
<div class="tab-content" id="tester-tab">
<h2>API Tester</h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 1rem;">
<div style="display: flex; flex-direction: column; gap: 1rem;">
<div class="guide-step">
<h4>Request</h4>
<div style="margin-top: 0.5rem;">
<label for="api-key" style="display: block; margin-bottom: 0.25rem;">API Key:</label>
<input type="password" id="api-key" placeholder="user_..." style="width: 100%; padding: 0.5rem; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; color: var(--text-primary);">
</div>
<div style="margin-top: 0.5rem;">
<label for="model-select" style="display: block; margin-bottom: 0.25rem;">Model:</label>
<select id="model-select" style="width: 100%; padding: 0.5rem; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; color: var(--text-primary);">
<option value="claude-3.7-sonnet">claude-3.7-sonnet</option>
</select>
</div>
<div style="margin-top: 0.5rem;">
<label for="prompt-input" style="display: block; margin-bottom: 0.25rem;">Prompt:</label>
<textarea id="prompt-input" placeholder="Enter your prompt here..." style="width: 100%; height: 120px; padding: 0.5rem; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; color: var(--text-primary); resize: vertical;">Hello, can you introduce yourself?</textarea>
</div>
<div style="margin-top: 0.5rem; display: flex; gap: 0.5rem; align-items: center;">
<label for="temperature" style="flex-shrink: 0;">Temperature:</label>
<input type="range" id="temperature" min="0" max="1" step="0.1" value="0.7" style="flex-grow: 1;">
<span id="temperature-value">0.7</span>
</div>
<button id="submit-api" style="margin-top: 1rem; width: 100%; padding: 0.75rem; background: var(--accent-color); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: 500; transition: var(--transition);">Send Request</button>
</div>
</div>
<div class="guide-step">
<h4>Response</h4>
<div style="height: 300px; overflow-y: auto; margin-top: 0.5rem; padding: 0.75rem; background: var(--bg-tertiary); border-radius: 4px; font-family: monospace; white-space: pre-wrap; font-size: 0.8rem;" id="api-response">
Response will appear here...
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<script>
// Theme Toggle
const themeToggle = document.getElementById('theme-toggle');
// Check system preference
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
themeToggle.checked = true;
}
// Toggle theme
themeToggle.addEventListener('change', function() {
if (this.checked) {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.removeAttribute('data-theme');
}
});
// Mobile Menu Toggle
const menuToggle = document.getElementById('menu-toggle');
const sidebar = document.getElementById('sidebar');
if (menuToggle) {
menuToggle.addEventListener('click', function() {
sidebar.classList.toggle('active');
});
}
// Main Tab Navigation
const tabButtons = document.querySelectorAll('.tab-button');
const tabContents = document.querySelectorAll('.tab-content');
const navItems = document.querySelectorAll('.nav-item');
function setActiveTab(tabId) {
// Deactivate all tabs
tabButtons.forEach(button => button.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
navItems.forEach(item => item.classList.remove('active'));
// Activate the selected tab
document.querySelector(`.tab-button[data-tab="${tabId}"]`)?.classList.add('active');
document.getElementById(`${tabId}-tab`)?.classList.add('active');
document.querySelector(`.nav-item[data-tab="${tabId}"]`)?.classList.add('active');
}
// Set up tab click handlers
tabButtons.forEach(button => {
button.addEventListener('click', function() {
const tabId = this.getAttribute('data-tab');
setActiveTab(tabId);
});
});
// Set up nav item click handlers
navItems.forEach(item => {
item.addEventListener('click', function() {
const tabId = this.getAttribute('data-tab');
setActiveTab(tabId);
// Close mobile menu if open
if (window.innerWidth < 769) {
sidebar.classList.remove('active');
}
});
});
// Code example tabs
const codeTabButtons = document.querySelectorAll('[data-code-tab]');
const codeTabs = document.querySelectorAll('.code-tab');
codeTabButtons.forEach(button => {
button.addEventListener('click', function() {
codeTabButtons.forEach(btn => btn.classList.remove('active'));
codeTabs.forEach(tab => tab.classList.remove('active'));
const tabId = this.getAttribute('data-code-tab');
this.classList.add('active');
document.getElementById(`${tabId}-code`).classList.add('active');
});
});
// Copy buttons
document.querySelectorAll('.copy-btn').forEach(button => {
button.addEventListener('click', function() {
const codeBlock = this.parentElement;
const code = codeBlock.textContent.trim();
navigator.clipboard.writeText(code).then(() => {
this.textContent = '✓';
setTimeout(() => {
this.textContent = '📋';
}, 1500);
});
});
});
// Get and display endpoint URL
const url = new URL(window.location.href);
const link = url.protocol + '//' + url.host + '/hf/v1';
// Update all endpoint URL displays
document.querySelectorAll('#endpoint-url, #endpoint-guide').forEach(el => {
el.textContent = link;
});
document.getElementById('endpoint-shorthand').textContent = link + '/chat/completions';
// Temperature slider
const temperatureSlider = document.getElementById('temperature');
const temperatureValue = document.getElementById('temperature-value');
if (temperatureSlider) {
temperatureSlider.addEventListener('input', function() {
temperatureValue.textContent = this.value;
});
}
// Load models list
fetch('/hf/v1/models')
.then(response => response.json())
.then(data => {
// Update model counts
document.getElementById('model-count').textContent = data.data.length;
// Count models by category
let claudeCount = 0;
let gptCount = 0;
let geminiCount = 0;
let otherCount = 0;
data.data.forEach(model => {
if (model.id.includes('claude')) claudeCount++;
else if (model.id.includes('gpt')) gptCount++;
else if (model.id.includes('gemini')) geminiCount++;
else otherCount++;
});
document.getElementById('claude-count').textContent = claudeCount;
document.getElementById('gpt-count').textContent = gptCount;
document.getElementById('gemini-count').textContent = geminiCount;
document.getElementById('other-count').textContent = otherCount;
// Featured models (for dashboard)
const featuredModels = ['claude-3.7-sonnet', 'gpt-4o', 'claude-3.5-sonnet', 'gemini-1.5-flash-500k'];
const featuredContainer = document.getElementById('featured-models');
featuredContainer.innerHTML = '';
featuredModels.forEach(modelId => {
const found = data.data.find(m => m.id === modelId);
if (found) {
const modelEl = document.createElement('div');
modelEl.className = 'model-tag';
modelEl.textContent = found.id;
featuredContainer.appendChild(modelEl);
}
});
// All models
const allModelsContainer = document.getElementById('all-models');
allModelsContainer.innerHTML = '';
// Sort models alphabetically
data.data.sort((a, b) => a.id.localeCompare(b.id));
data.data.forEach(model => {
const modelEl = document.createElement('div');
modelEl.className = 'model-tag';
modelEl.textContent = model.id;
allModelsContainer.appendChild(modelEl);
// Add to model select dropdown
const option = document.createElement('option');
option.value = model.id;
option.textContent = model.id;
document.getElementById('model-select').appendChild(option);
});
})
.catch(err => {
document.querySelectorAll('#all-models, #featured-models').forEach(el => {
el.textContent = 'Failed to load models: ' + err.message;
});
});
// API Tester
const submitApiBtn = document.getElementById('submit-api');
if (submitApiBtn) {
submitApiBtn.addEventListener('click', async function() {
const apiKey = document.getElementById('api-key').value;
const model = document.getElementById('model-select').value;
const prompt = document.getElementById('prompt-input').value;
const temperature = parseFloat(document.getElementById('temperature').value);
const responseContainer = document.getElementById('api-response');
responseContainer.textContent = 'Loading...';
try {
const response = await fetch(`${link}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: model,
messages: [{ role: 'user', content: prompt }],
temperature: temperature
})
});
const data = await response.json();
responseContainer.textContent = JSON.stringify(data, null, 2);
} catch (error) {
responseContainer.textContent = 'Error: ' + (error.message || 'Unknown error');
}
});
}
</script>
</body>
</html>
`;
res.send(htmlContent);
});
const port = process.env.HF_PORT || 7860;
app.listen(port, () => {
console.log(`HF Proxy server is running at PORT: ${port}`);
console.log(`Proxy status: ${proxyConfig ? 'Enabled' : 'Disabled'}`);
});