carrzy commited on
Commit
00a4c73
·
verified ·
1 Parent(s): e7a6e8f

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +27 -0
  2. README.md +4 -3
  3. gitattributes +35 -0
  4. hf.js +461 -0
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ghcr.io/jiuz-chn/cursor-to-openai:latest
2
+
3
+ WORKDIR /app
4
+
5
+ COPY hf.js /app/hf.js
6
+
7
+ RUN npm install http-proxy-middleware axios express morgan
8
+
9
+ # 服务端口
10
+ ENV HF_PORT=7860
11
+
12
+ # 代理配置 - 格式: "http://user:pass@host:port,http://user2:pass2@host2:port2"
13
+ ENV PROXY=""
14
+
15
+ # 目标服务地址 - 接收转发请求的实际服务
16
+ ENV TARGET_URL="http://localhost:3010"
17
+
18
+ # 模型列表API端点可选前缀 - 如/v1,/api/v1等
19
+ ENV API_PATH="/v1"
20
+
21
+ # 请求超时时间(毫秒)
22
+ ENV TIMEOUT=30000
23
+
24
+ EXPOSE 7860
25
+
26
+ CMD ["node", "hf.js"]
27
+
README.md CHANGED
@@ -1,10 +1,11 @@
1
  ---
2
  title: Cursor Ai Proxy
3
- emoji: 🐠
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Cursor Ai Proxy
3
+ emoji: 🏆
4
+ colorFrom: yellow
5
+ colorTo: pink
6
  sdk: docker
7
  pinned: false
8
+ license: mit
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
hf.js ADDED
@@ -0,0 +1,461 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const morgan = require('morgan');
3
+ const { createProxyMiddleware } = require('http-proxy-middleware');
4
+ const axios = require('axios');
5
+ const url = require('url');
6
+ const app = express();
7
+
8
+ // 启用日志
9
+ app.use(morgan('dev'));
10
+
11
+ // 环境变量配置
12
+ const PORT = process.env.HF_PORT || 7860;
13
+ const TARGET_URL = process.env.TARGET_URL || 'http://localhost:3010';
14
+ const API_PATH = process.env.API_PATH || '/v1';
15
+ const TIMEOUT = parseInt(process.env.TIMEOUT) || 30000;
16
+
17
+ console.log(`Service configuration:
18
+ - Port: ${PORT}
19
+ - Target URL: ${TARGET_URL}
20
+ - API Path: ${API_PATH}
21
+ - Timeout: ${TIMEOUT}ms`);
22
+
23
+ // 解析代理设置
24
+ let proxyPool = [];
25
+ if (process.env.PROXY) {
26
+ proxyPool = process.env.PROXY.split(',').map(p => p.trim()).filter(p => p);
27
+ console.log(`Loaded ${proxyPool.length} proxies from environment`);
28
+
29
+ if (proxyPool.length > 0) {
30
+ console.log('Proxy pool initialized:');
31
+ proxyPool.forEach((proxy, index) => {
32
+ // 隐藏敏感信息的日志
33
+ const maskedProxy = proxy.replace(/(https?:\/\/)([^:]+):([^@]+)@/, '$1$2:****@');
34
+ console.log(` [${index + 1}] ${maskedProxy}`);
35
+ });
36
+ }
37
+ }
38
+
39
+ // 从代理池中随机选择一个代理
40
+ function getRandomProxy() {
41
+ if (proxyPool.length === 0) return null;
42
+ const randomIndex = Math.floor(Math.random() * proxyPool.length);
43
+ const proxyUrl = proxyPool[randomIndex];
44
+ const parsedUrl = url.parse(proxyUrl);
45
+
46
+ return {
47
+ host: parsedUrl.hostname,
48
+ port: parsedUrl.port || 80,
49
+ auth: parsedUrl.auth ? {
50
+ username: parsedUrl.auth.split(':')[0],
51
+ password: parsedUrl.auth.split(':')[1]
52
+ } : undefined
53
+ };
54
+ }
55
+
56
+ // 模型列表 API
57
+ app.get('/hf/v1/models', (req, res) => {
58
+ const models = {
59
+ "object": "list",
60
+ "data": [
61
+ {
62
+ "id": "claude-3.5-sonnet",
63
+ "object": "model",
64
+ "created": 1706745938,
65
+ "owned_by": "cursor"
66
+ },
67
+ {
68
+ "id": "gpt-4",
69
+ "object": "model",
70
+ "created": 1706745938,
71
+ "owned_by": "cursor"
72
+ },
73
+ {
74
+ "id": "gpt-4o",
75
+ "object": "model",
76
+ "created": 1706745938,
77
+ "owned_by": "cursor"
78
+ },
79
+ {
80
+ "id": "claude-3-opus",
81
+ "object": "model",
82
+ "created": 1706745938,
83
+ "owned_by": "cursor"
84
+ },
85
+ {
86
+ "id": "gpt-3.5-turbo",
87
+ "object": "model",
88
+ "created": 1706745938,
89
+ "owned_by": "cursor"
90
+ },
91
+ {
92
+ "id": "gpt-4-turbo-2024-04-09",
93
+ "object": "model",
94
+ "created": 1706745938,
95
+ "owned_by": "cursor"
96
+ },
97
+ {
98
+ "id": "gpt-4o-128k",
99
+ "object": "model",
100
+ "created": 1706745938,
101
+ "owned_by": "cursor"
102
+ },
103
+ {
104
+ "id": "gemini-1.5-flash-500k",
105
+ "object": "model",
106
+ "created": 1706745938,
107
+ "owned_by": "cursor"
108
+ },
109
+ {
110
+ "id": "claude-3-haiku-200k",
111
+ "object": "model",
112
+ "created": 1706745938,
113
+ "owned_by": "cursor"
114
+ },
115
+ {
116
+ "id": "claude-3-5-sonnet-200k",
117
+ "object": "model",
118
+ "created": 1706745938,
119
+ "owned_by": "cursor"
120
+ },
121
+ {
122
+ "id": "claude-3-5-sonnet-20241022",
123
+ "object": "model",
124
+ "created": 1706745938,
125
+ "owned_by": "cursor"
126
+ },
127
+ {
128
+ "id": "gpt-4o-mini",
129
+ "object": "model",
130
+ "created": 1706745938,
131
+ "owned_by": "cursor"
132
+ },
133
+ {
134
+ "id": "o1-mini",
135
+ "object": "model",
136
+ "created": 1706745938,
137
+ "owned_by": "cursor"
138
+ },
139
+ {
140
+ "id": "o1-preview",
141
+ "object": "model",
142
+ "created": 1706745938,
143
+ "owned_by": "cursor"
144
+ },
145
+ {
146
+ "id": "o1",
147
+ "object": "model",
148
+ "created": 1706745938,
149
+ "owned_by": "cursor"
150
+ },
151
+ {
152
+ "id": "claude-3.5-haiku",
153
+ "object": "model",
154
+ "created": 1706745938,
155
+ "owned_by": "cursor"
156
+ },
157
+ {
158
+ "id": "gemini-exp-1206",
159
+ "object": "model",
160
+ "created": 1706745938,
161
+ "owned_by": "cursor"
162
+ },
163
+ {
164
+ "id": "gemini-2.0-flash-thinking-exp",
165
+ "object": "model",
166
+ "created": 1706745938,
167
+ "owned_by": "cursor"
168
+ },
169
+ {
170
+ "id": "gemini-2.0-flash-exp",
171
+ "object": "model",
172
+ "created": 1706745938,
173
+ "owned_by": "cursor"
174
+ },
175
+ {
176
+ "id": "deepseek-v3",
177
+ "object": "model",
178
+ "created": 1706745938,
179
+ "owned_by": "cursor"
180
+ },
181
+ {
182
+ "id": "deepseek-r1",
183
+ "object": "model",
184
+ "created": 1706745938,
185
+ "owned_by": "cursor"
186
+ }
187
+ ]
188
+ };
189
+ res.json(models);
190
+ });
191
+
192
+ // 代理转发,使用动态代理池
193
+ app.use('/hf/v1/chat/completions', (req, res, next) => {
194
+ const proxy = getRandomProxy();
195
+ const targetEndpoint = `${TARGET_URL}${API_PATH}/chat/completions`;
196
+
197
+ console.log(`Forwarding request to: ${targetEndpoint}`);
198
+
199
+ const middleware = createProxyMiddleware({
200
+ target: targetEndpoint,
201
+ changeOrigin: true,
202
+ proxy: proxy ? proxy : undefined,
203
+ timeout: TIMEOUT,
204
+ proxyTimeout: TIMEOUT,
205
+ onProxyReq: (proxyReq, req, res) => {
206
+ if (req.body) {
207
+ const bodyData = JSON.stringify(req.body);
208
+ proxyReq.setHeader('Content-Type', 'application/json');
209
+ proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
210
+ proxyReq.write(bodyData);
211
+ proxyReq.end();
212
+ }
213
+ },
214
+ onError: (err, req, res) => {
215
+ console.error('Proxy error:', err);
216
+ res.status(500).json({
217
+ error: {
218
+ message: 'Proxy error occurred',
219
+ type: 'proxy_error',
220
+ details: process.env.NODE_ENV === 'development' ? err.message : undefined
221
+ }
222
+ });
223
+ },
224
+ onProxyRes: (proxyRes, req, res) => {
225
+ console.log(`Proxy response status: ${proxyRes.statusCode}`);
226
+ }
227
+ });
228
+
229
+ if (proxy) {
230
+ const maskedProxy = `${proxy.host}:${proxy.port}` + (proxy.auth ? ' (with auth)' : '');
231
+ console.log(`Using proxy: ${maskedProxy}`);
232
+ } else {
233
+ console.log('Direct connection (no proxy)');
234
+ }
235
+
236
+ middleware(req, res, next);
237
+ });
238
+
239
+ // 首页
240
+ app.get('/', (req, res) => {
241
+ const htmlContent = `
242
+ <!DOCTYPE html>
243
+ <html lang="en">
244
+ <head>
245
+ <meta charset="UTF-8">
246
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
247
+ <title>Cursor To OpenAI</title>
248
+ <style>
249
+ :root {
250
+ --primary-color: #2563eb;
251
+ --bg-color: #f8fafc;
252
+ --card-bg: #ffffff;
253
+ }
254
+ body {
255
+ padding: 20px;
256
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
257
+ max-width: 1000px;
258
+ margin: 0 auto;
259
+ line-height: 1.6;
260
+ background: var(--bg-color);
261
+ color: #1a1a1a;
262
+ }
263
+ .container {
264
+ padding: 20px;
265
+ }
266
+ .header {
267
+ text-align: center;
268
+ margin-bottom: 40px;
269
+ }
270
+ .header h1 {
271
+ color: var(--primary-color);
272
+ font-size: 2.5em;
273
+ margin-bottom: 10px;
274
+ }
275
+ .info {
276
+ background: #fff;
277
+ padding: 25px;
278
+ border-radius: 12px;
279
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
280
+ margin-bottom: 30px;
281
+ border: 1px solid #e5e7eb;
282
+ }
283
+ .info-item {
284
+ margin: 15px 0;
285
+ padding: 10px;
286
+ background: #f8fafc;
287
+ border-radius: 8px;
288
+ border: 1px solid #e5e7eb;
289
+ }
290
+ .info-label {
291
+ color: #4b5563;
292
+ font-size: 0.9em;
293
+ margin-bottom: 5px;
294
+ }
295
+ .info-value {
296
+ color: var(--primary-color);
297
+ font-weight: 500;
298
+ }
299
+ .service-status {
300
+ background: #dcfce7;
301
+ border: 1px solid #86efac;
302
+ color: #166534;
303
+ padding: 8px 12px;
304
+ border-radius: 6px;
305
+ font-size: 0.95em;
306
+ display: inline-block;
307
+ margin: 15px 0;
308
+ }
309
+ .models {
310
+ background: var(--card-bg);
311
+ padding: 25px;
312
+ border-radius: 12px;
313
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
314
+ border: 1px solid #e5e7eb;
315
+ }
316
+ .models h3 {
317
+ color: #1a1a1a;
318
+ margin-bottom: 20px;
319
+ font-size: 1.5em;
320
+ border-bottom: 2px solid #e5e7eb;
321
+ padding-bottom: 10px;
322
+ }
323
+ .model-item {
324
+ margin: 12px 0;
325
+ padding: 15px;
326
+ background: #f8fafc;
327
+ border-radius: 8px;
328
+ border: 1px solid #e5e7eb;
329
+ transition: all 0.3s ease;
330
+ display: flex;
331
+ justify-content: space-between;
332
+ align-items: center;
333
+ }
334
+ .model-item:hover {
335
+ transform: translateY(-2px);
336
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
337
+ }
338
+ .model-name {
339
+ font-weight: 500;
340
+ color: #1a1a1a;
341
+ }
342
+ .model-provider {
343
+ color: #6b7280;
344
+ font-size: 0.9em;
345
+ padding: 4px 8px;
346
+ background: #f1f5f9;
347
+ border-radius: 4px;
348
+ }
349
+ .details {
350
+ margin-top: 20px;
351
+ background: #f8fafc;
352
+ padding: 15px;
353
+ border-radius: 8px;
354
+ border: 1px solid #e5e7eb;
355
+ }
356
+ .details pre {
357
+ background: #f1f5f9;
358
+ padding: 10px;
359
+ border-radius: 6px;
360
+ overflow-x: auto;
361
+ }
362
+ @media (max-width: 768px) {
363
+ body {
364
+ padding: 10px;
365
+ }
366
+ .container {
367
+ padding: 10px;
368
+ }
369
+ }
370
+ </style>
371
+ </head>
372
+ <body>
373
+ <div class="container">
374
+ <div class="header">
375
+ <h1>Cursor To OpenAI Server</h1>
376
+ <p>高性能 AI 模型代理服务</p>
377
+ <div class="service-status">服��正在运行</div>
378
+ </div>
379
+
380
+ <div class="info">
381
+ <h2>配置信息</h2>
382
+ <div class="info-item">
383
+ <div class="info-label">服务环境</div>
384
+ <div class="info-value">环境变量配置模式</div>
385
+ </div>
386
+ <div class="info-item">
387
+ <div class="info-label">自定义端点(基本URL)</div>
388
+ <div class="info-value" id="endpoint-url"></div>
389
+ </div>
390
+ <div class="info-item">
391
+ <div class="info-label">目标服务</div>
392
+ <div class="info-value">${TARGET_URL}${API_PATH}</div>
393
+ </div>
394
+ <div class="info-item">
395
+ <div class="info-label">代理状态</div>
396
+ <div class="info-value">${proxyPool.length > 0 ? `使用中 (${proxyPool.length}个代理)` : '未启用'}</div>
397
+ </div>
398
+ </div>
399
+
400
+ <div class="models">
401
+ <h3>支持的模型列表</h3>
402
+ <div id="model-list"></div>
403
+ </div>
404
+
405
+ <div class="details">
406
+ <h3>使用说明</h3>
407
+ <p>在客户端配置以下信息:</p>
408
+ <pre>API URL: http://{服务器地址}:${PORT}/hf/v1
409
+ API Key: 您的API密钥</pre>
410
+ <p>支持兼容OpenAI格式的请求,已预配置转发到${TARGET_URL}${API_PATH}</p>
411
+ </div>
412
+ </div>
413
+
414
+ <script>
415
+ const url = new URL(window.location.href);
416
+ const link = url.protocol + '//' + url.host + '/hf/v1';
417
+ document.getElementById('endpoint-url').textContent = link;
418
+
419
+ fetch(link + '/models')
420
+ .then(response => response.json())
421
+ .then(data => {
422
+ const modelList = document.getElementById('model-list');
423
+ data.data.forEach(model => {
424
+ const div = document.createElement('div');
425
+ div.className = 'model-item';
426
+ div.innerHTML = \`
427
+ <span class="model-name">\${model.id}</span>
428
+ <span class="model-provider">\${model.owned_by}</span>
429
+ \`;
430
+ modelList.appendChild(div);
431
+ });
432
+ })
433
+ .catch(error => {
434
+ console.error('Error fetching models:', error);
435
+ document.getElementById('model-list').textContent = '获取模型列表失败';
436
+ });
437
+ </script>
438
+ </body>
439
+ </html>
440
+ `;
441
+ res.send(htmlContent);
442
+ });
443
+
444
+ // 健康检查端点
445
+ app.get('/health', (req, res) => {
446
+ res.status(200).json({
447
+ status: 'ok',
448
+ time: new Date().toISOString(),
449
+ proxyCount: proxyPool.length,
450
+ target: `${TARGET_URL}${API_PATH}`
451
+ });
452
+ });
453
+
454
+ // 启动服务
455
+ app.listen(PORT, () => {
456
+ console.log(`HF Proxy server is running at PORT: ${PORT}`);
457
+ console.log(`Target service: ${TARGET_URL}${API_PATH}`);
458
+ console.log(`Proxy status: ${proxyPool.length > 0 ? 'Enabled' : 'Disabled'}`);
459
+ });
460
+
461
+