isididiidid's picture
Update hf.js
879c88f verified
const express = require('express');
const morgan = require('morgan');
const { createProxyMiddleware } = require('http-proxy-middleware');
const axios = require('axios');
const fs = require('fs');
const url = require('url');
const app = express();
app.use(morgan('dev'));
// 全局代理池
let proxyPool = process.env.PROXY ? process.env.PROXY.split(',').map(p => p.trim()) : [];
console.log('Initial proxy pool:', proxyPool);
// 从代理池中随机选择一个代理
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
};
}
// 配置 axios 的代理
function configureAxiosProxy() {
const proxy = getRandomProxy();
if (proxy) {
axios.defaults.proxy = proxy;
console.log(`Axios using proxy: ${proxy.host}:${proxy.port}`);
} else {
delete axios.defaults.proxy;
console.log('No proxy available for axios');
}
}
// 从外部 API 更新代理池
async function updateProxyPool() {
const proxyApiUrl = process.env.PROXY_API_URL || 'http://example.com/api/proxies';
try {
const response = await axios.get(proxyApiUrl);
// 假设 API 返回格式为 { proxies: ["http://user:pass@host:port", ...] }
const newProxies = response.data.proxies || [];
if (newProxies.length > 0) {
proxyPool = newProxies;
console.log('Proxy pool updated:', proxyPool);
configureAxiosProxy(); // 更新 axios 代理
} else {
console.warn('No proxies received from API');
}
} catch (error) {
console.error('Failed to update proxy pool:', error.message);
}
}
// 定期更新代理池
const updateInterval = parseInt(process.env.PROXY_UPDATE_INTERVAL) || 300; // 默认 5 分钟
if (updateInterval > 0) {
setInterval(updateProxyPool, updateInterval * 1000);
console.log(`Proxy pool will update every ${updateInterval} seconds`);
// 启动时立即更新一次
updateProxyPool();
}
// 模型列表 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);
});
// 代理转发,使用动态代理池
app.use('/hf/v1/chat/completions', (req, res, next) => {
const proxy = getRandomProxy();
const middleware = createProxyMiddleware({
target: 'http://localhost:3010/v1/chat/completions', // 可替换为实际目标服务
changeOrigin: true,
proxy: proxy ? proxy : undefined,
timeout: 30000, // 超时 30 秒
proxyTimeout: 30000, // 代理超时 30 秒
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).send('Proxy error occurred');
}
});
if (proxy) {
console.log(`Request proxied via ${proxy.host}:${proxy.port}`);
} else {
console.log('No proxy available, direct connection');
}
middleware(req, res, next);
});
// 首页
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>
:root {
--primary-color: #2563eb;
--bg-color: #f8fafc;
--card-bg: #ffffff;
}
body {
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 1000px;
margin: 0 auto;
line-height: 1.6;
background: var(--bg-color);
color: #1a1a1a;
}
.container {
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 40px;
}
.header h1 {
color: var(--primary-color);
font-size: 2.5em;
margin-bottom: 10px;
}
.info {
background: #fff;
padding: 25px;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
border: 1px solid #e5e7eb;
}
.info-item {
margin: 15px 0;
padding: 10px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e5e7eb;
}
.info-label {
color: #4b5563;
font-size: 0.9em;
margin-bottom: 5px;
}
.info-value {
color: var(--primary-color);
font-weight: 500;
}
.models {
background: var(--card-bg);
padding: 25px;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
border: 1px solid #e5e7eb;
}
.models h3 {
color: #1a1a1a;
margin-bottom: 20px;
font-size: 1.5em;
border-bottom: 2px solid #e5e7eb;
padding-bottom: 10px;
}
.model-item {
margin: 12px 0;
padding: 15px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e5e7eb;
transition: all 0.3s ease;
display: flex;
justify-content: space-between;
align-items: center;
}
.model-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.model-name {
font-weight: 500;
color: #1a1a1a;
}
.model-provider {
color: #6b7280;
font-size: 0.9em;
padding: 4px 8px;
background: #f1f5f9;
border-radius: 4px;
}
@media (max-width: 768px) {
body {
padding: 10px;
}
.container {
padding: 10px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Cursor To OpenAI Server</h1>
<p>高性能 AI 模型代理服务</p>
</div>
<div class="info">
<h2>配置信息</h2>
<div class="info-item">
<div class="info-label">聊天来源</div>
<div class="info-value">自定义(兼容 OpenAI)</div>
</div>
<div class="info-item">
<div class="info-label">自定义端点(基本URL)</div>
<div class="info-value" id="endpoint-url"></div>
</div>
<div class="info-item">
<div class="info-label">自定义API密钥</div>
<div class="info-value">抓取的Cursor Cookie,格式为user_...</div>
</div>
</div>
<div class="models">
<h3>支持的模型列表</h3>
<div id="model-list"></div>
</div>
</div>
<script>
const url = new URL(window.location.href);
const link = url.protocol + '//' + url.host + '/hf/v1';
document.getElementById('endpoint-url').textContent = link;
fetch(link + '/models')
.then(response => response.json())
.then(data => {
const modelList = document.getElementById('model-list');
data.data.forEach(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>
\`;
modelList.appendChild(div);
});
})
.catch(error => {
console.error('Error fetching models:', error);
document.getElementById('model-list').textContent = '获取模型列表失败';
});
</script>
</body>
</html>
`;
res.send(htmlContent);
});
// 启动服务前配置初始 axios 代理
configureAxiosProxy();
const port = process.env.HF_PORT || 7860;
app.listen(port, () => {
console.log(`HF Proxy server is running at PORT: ${port}`);
});