| | 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')); |
| |
|
| | |
| | const proxyUrl = process.env.PROXY || ''; |
| | console.log(`Proxy configuration: ${proxyUrl ? 'Configured' : 'Not configured'}`); |
| | console.log(`Raw proxy URL: ${proxyUrl}`); |
| |
|
| | |
| | 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 |
| | }; |
| | |
| | |
| | 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); |
| | } |
| | } |
| |
|
| | |
| | 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" |
| | }, |
| | |
| | { |
| | "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); |
| | }); |
| |
|
| | |
| | app.use('/hf/v1/chat/completions', createProxyMiddleware({ |
| | target: 'http://localhost:3010/v1/chat/completions', |
| | changeOrigin: true, |
| | |
| | proxy: proxyConfig, |
| | |
| | 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 |
| | const tabButton = document.querySelector('.tab-button[data-tab="' + tabId + '"]'); |
| | if (tabButton) { |
| | tabButton.classList.add('active'); |
| | } |
| | |
| | const tabContent = document.getElementById(tabId + '-tab'); |
| | if (tabContent) { |
| | tabContent.classList.add('active'); |
| | } |
| | |
| | const navItem = document.querySelector('.nav-item[data-tab="' + tabId + '"]'); |
| | if (navItem) { |
| | navItem.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'); |
| | |
| | const codeTab = document.getElementById(tabId + '-code'); |
| | if (codeTab) { |
| | codeTab.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; |
| | }); |
| | |
| | const endpointShorthand = document.getElementById('endpoint-shorthand'); |
| | if (endpointShorthand) { |
| | endpointShorthand.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 |
| | const modelCount = document.getElementById('model-count'); |
| | if (modelCount) { |
| | modelCount.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++; |
| | }); |
| | |
| | const claudeCountEl = document.getElementById('claude-count'); |
| | if (claudeCountEl) { |
| | claudeCountEl.textContent = claudeCount; |
| | } |
| | |
| | const gptCountEl = document.getElementById('gpt-count'); |
| | if (gptCountEl) { |
| | gptCountEl.textContent = gptCount; |
| | } |
| | |
| | const geminiCountEl = document.getElementById('gemini-count'); |
| | if (geminiCountEl) { |
| | geminiCountEl.textContent = geminiCount; |
| | } |
| | |
| | const otherCountEl = document.getElementById('other-count'); |
| | if (otherCountEl) { |
| | otherCountEl.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'); |
| | if (featuredContainer) { |
| | 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'); |
| | if (allModelsContainer) { |
| | 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 modelSelect = document.getElementById('model-select'); |
| | if (modelSelect) { |
| | const option = document.createElement('option'); |
| | option.value = model.id; |
| | option.textContent = model.id; |
| | modelSelect.appendChild(option); |
| | } |
| | }); |
| | } |
| | }) |
| | .catch(err => { |
| | const allModelsEl = document.getElementById('all-models'); |
| | if (allModelsEl) { |
| | allModelsEl.textContent = 'Failed to load models: ' + err.message; |
| | } |
| | |
| | const featuredModelsEl = document.getElementById('featured-models'); |
| | if (featuredModelsEl) { |
| | featuredModelsEl.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'}`); |
| | }); |