|
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 ? '已配置' : '未配置'}`); |
|
|
|
|
|
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 |
|
}; |
|
|
|
|
|
const maskedConfig = { |
|
...proxyConfig, |
|
auth: proxyConfig.auth ? { |
|
username: proxyConfig.auth.username, |
|
password: '******' |
|
} : undefined |
|
}; |
|
console.log('Using proxy:', JSON.stringify(maskedConfig)); |
|
} 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> |
|
body { |
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
|
max-width: 800px; |
|
margin: 0 auto; |
|
padding: 20px; |
|
line-height: 1.6; |
|
} |
|
.container { |
|
background: #f9f9f9; |
|
border-radius: 10px; |
|
padding: 20px; |
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
|
} |
|
.info-item { |
|
margin-bottom: 10px; |
|
} |
|
.status { |
|
background: ${proxyConfig ? '#e1f5e1' : '#fff3cd'}; |
|
padding: 10px; |
|
border-radius: 5px; |
|
margin-top: 20px; |
|
border: 1px solid ${proxyConfig ? '#c3e6cb' : '#ffeeba'}; |
|
} |
|
.models-container { |
|
margin-top: 20px; |
|
border-top: 1px solid #eee; |
|
padding-top: 20px; |
|
} |
|
.model-list { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); |
|
gap: 10px; |
|
margin-top: 15px; |
|
} |
|
.model-item { |
|
background: #f0f0f0; |
|
padding: 8px 12px; |
|
border-radius: 4px; |
|
font-size: 0.9em; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<h1>Cursor To OpenAI Server</h1> |
|
<div class="info-item"> |
|
<strong>聊天来源:</strong> 自定义(兼容 OpenAI) |
|
</div> |
|
<div class="info-item"> |
|
<strong>自定义端点(基本URL):</strong><span id="endpoint-url"></span> |
|
</div> |
|
<div class="info-item"> |
|
<strong>自定义API密钥:</strong>[抓取的Cursor Cookie,格式为user_...] |
|
</div> |
|
<div class="status"> |
|
<strong>代理状态:</strong> ${proxyConfig ? '已启用' : '未启用'} |
|
${proxyConfig ? `<p>代理服务器: ${proxyConfig.host}:${proxyConfig.port}</p>` : ''} |
|
</div> |
|
|
|
<div class="models-container"> |
|
<h3>支持的模型</h3> |
|
<div id="model-list" class="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('/hf/v1/models') |
|
.then(response => response.json()) |
|
.then(data => { |
|
const modelListEl = document.getElementById('model-list'); |
|
modelListEl.innerHTML = ''; |
|
|
|
data.data.forEach(model => { |
|
const modelEl = document.createElement('div'); |
|
modelEl.className = 'model-item'; |
|
modelEl.textContent = model.id; |
|
modelListEl.appendChild(modelEl); |
|
}); |
|
}) |
|
.catch(err => { |
|
document.getElementById('model-list').textContent = '加载模型失败: ' + err.message; |
|
}); |
|
</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'}`); |
|
}); |
|
|