Spaces:
Runtime error
Runtime error
Update index.js
Browse files
index.js
CHANGED
@@ -6,13 +6,14 @@ import cors from 'cors';
|
|
6 |
import Logger from './logger.js';
|
7 |
import dotenv from 'dotenv';
|
8 |
|
|
|
9 |
// 初始化环境变量
|
10 |
dotenv.config();
|
11 |
|
12 |
// 配置管理
|
13 |
const CONFIG = {
|
14 |
SERVER: {
|
15 |
-
PORT: process.env.PORT ||
|
16 |
BODY_LIMIT: '5mb',
|
17 |
CORS_OPTIONS: {
|
18 |
origin: '*',
|
@@ -38,6 +39,7 @@ const CONFIG = {
|
|
38 |
"grok-3-reasoning": "grok-3",
|
39 |
"grok-3-imageGen": "grok-3",
|
40 |
},
|
|
|
41 |
IS_IMG_GEN: false,
|
42 |
IS_THINKING: false
|
43 |
};
|
@@ -58,6 +60,82 @@ const DEFAULT_HEADERS = {
|
|
58 |
'priority': 'u=1, i'
|
59 |
};
|
60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
// 工具类
|
62 |
class Utils {
|
63 |
static generateRandomString(length, charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
|
@@ -65,12 +143,17 @@ class Utils {
|
|
65 |
.map(() => charset[Math.floor(Math.random() * charset.length)])
|
66 |
.join('');
|
67 |
}
|
68 |
-
|
|
|
|
|
|
|
|
|
|
|
69 |
static createAuthHeaders() {
|
70 |
return {
|
71 |
...DEFAULT_HEADERS,
|
72 |
-
'x-csrf-token': CONFIG.
|
73 |
-
'cookie':
|
74 |
};
|
75 |
}
|
76 |
|
@@ -102,6 +185,10 @@ class ConversationManager {
|
|
102 |
const data = await Utils.handleApiResponse(response, '创建会话失败!');
|
103 |
return data.data.create_grok_conversation.conversation_id;
|
104 |
}
|
|
|
|
|
|
|
|
|
105 |
|
106 |
static async deleteConversation(conversationId) {
|
107 |
if (!conversationId) return;
|
@@ -417,7 +504,6 @@ class ResponseHandler {
|
|
417 |
let buffer = '';
|
418 |
let fullResponse = '';
|
419 |
let imageUrl = null;
|
420 |
-
|
421 |
try {
|
422 |
for await (const chunk of reader) {
|
423 |
const lines = (buffer + chunk.toString()).split('\n');
|
@@ -435,6 +521,9 @@ class ResponseHandler {
|
|
435 |
if (imageUrl) {
|
436 |
await this.sendImageResponse(imageUrl, model, res);
|
437 |
} else {
|
|
|
|
|
|
|
438 |
const responseData = MessageProcessor.createChatResponse(fullResponse, model);
|
439 |
res.json(responseData);
|
440 |
}
|
@@ -515,7 +604,7 @@ app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
|
|
515 |
app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
|
516 |
|
517 |
// API 路由
|
518 |
-
app.get('/
|
519 |
res.json({
|
520 |
object: "list",
|
521 |
data: Object.keys(CONFIG.MODELS).map(model => ({
|
@@ -526,30 +615,61 @@ app.get('/hf/v1/models', (req, res) => {
|
|
526 |
}))
|
527 |
});
|
528 |
});
|
529 |
-
|
530 |
-
app.post('/hf/v1/chat/completions', async (req, res) => {
|
531 |
try {
|
532 |
const authToken = req.headers.authorization?.replace('Bearer ', '');
|
533 |
if (authToken !== CONFIG.API.API_KEY) {
|
534 |
return res.status(401).json({ error: 'Unauthorized' });
|
535 |
}
|
536 |
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
548 |
}
|
549 |
|
550 |
-
|
551 |
-
|
552 |
-
: ResponseHandler.handleNormalResponse(response, req.body.model, res));
|
553 |
|
554 |
} catch (error) {
|
555 |
Logger.error('Chat Completions Request Error', error, 'ChatAPI');
|
@@ -561,16 +681,12 @@ app.post('/hf/v1/chat/completions', async (req, res) => {
|
|
561 |
code: error.code || null
|
562 |
}
|
563 |
});
|
564 |
-
} finally {
|
565 |
-
if (req.body.conversationId) {
|
566 |
-
await ConversationManager.deleteConversation(req.body.conversationId);
|
567 |
-
}
|
568 |
}
|
569 |
});
|
570 |
|
571 |
// 404 处理
|
572 |
app.use((req, res) => {
|
573 |
-
res.status(404).json({
|
574 |
});
|
575 |
|
576 |
// 启动服务器
|
|
|
6 |
import Logger from './logger.js';
|
7 |
import dotenv from 'dotenv';
|
8 |
|
9 |
+
|
10 |
// 初始化环境变量
|
11 |
dotenv.config();
|
12 |
|
13 |
// 配置管理
|
14 |
const CONFIG = {
|
15 |
SERVER: {
|
16 |
+
PORT: process.env.PORT || 7860,
|
17 |
BODY_LIMIT: '5mb',
|
18 |
CORS_OPTIONS: {
|
19 |
origin: '*',
|
|
|
39 |
"grok-3-reasoning": "grok-3",
|
40 |
"grok-3-imageGen": "grok-3",
|
41 |
},
|
42 |
+
SIGNATURE_INDEX: 0,
|
43 |
IS_IMG_GEN: false,
|
44 |
IS_THINKING: false
|
45 |
};
|
|
|
60 |
'priority': 'u=1, i'
|
61 |
};
|
62 |
|
63 |
+
|
64 |
+
async function initialization() {
|
65 |
+
const auth_tokenArray = CONFIG.API.AUTH_TOKEN.split(',');
|
66 |
+
const ct0Array = CONFIG.API.CT0.split(',');
|
67 |
+
auth_tokenArray.forEach((auth_token, index) => {
|
68 |
+
tokenManager.addToken(`auth_token=${auth_token};ct0=${ct0Array[index]}`);
|
69 |
+
});
|
70 |
+
Logger.info("初始化完成", 'Server');
|
71 |
+
}
|
72 |
+
class AuthTokenManager {
|
73 |
+
constructor() {
|
74 |
+
this.activeTokens = [];
|
75 |
+
this.expiredTokens = new Map();
|
76 |
+
this.isRecoveryProcess = false;
|
77 |
+
}
|
78 |
+
|
79 |
+
// 添加 token
|
80 |
+
addToken(token) {
|
81 |
+
if (!this.activeTokens.includes(token)) {
|
82 |
+
this.activeTokens.push(token);
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
// 通过下标获取 token
|
87 |
+
getTokenByIndex(index) {
|
88 |
+
if (index < 0 || index >= this.activeTokens.length) {
|
89 |
+
throw new Error(`无效的索引:${index}`);
|
90 |
+
}
|
91 |
+
return this.activeTokens[index];
|
92 |
+
}
|
93 |
+
|
94 |
+
// 通过下标移除 token
|
95 |
+
removeTokenByIndex(index) {
|
96 |
+
if (index < 0 || index >= this.activeTokens.length) {
|
97 |
+
throw new Error(`无效的索引:${index}`);
|
98 |
+
}
|
99 |
+
|
100 |
+
const token = this.activeTokens[index];
|
101 |
+
this.activeTokens.splice(index, 1);
|
102 |
+
|
103 |
+
// 记录失效时间
|
104 |
+
this.expiredTokens.set(token, Date.now());
|
105 |
+
return token;
|
106 |
+
}
|
107 |
+
|
108 |
+
// 启动定期恢复机制
|
109 |
+
startTokenRecoveryProcess() {
|
110 |
+
setInterval(() => {
|
111 |
+
const now = Date.now();
|
112 |
+
|
113 |
+
// 遍历并恢复过期的 token
|
114 |
+
for (const [token, expiredTime] of this.expiredTokens.entries()) {
|
115 |
+
if (now - expiredTime >= 1 * 60 * 1000) {
|
116 |
+
this.activeTokens.push(token);
|
117 |
+
this.expiredTokens.delete(token);
|
118 |
+
console.log(`Token ${token} recovered`);
|
119 |
+
}
|
120 |
+
}
|
121 |
+
}, 1 * 60 * 1000);
|
122 |
+
}
|
123 |
+
|
124 |
+
// 获取 token 总数
|
125 |
+
getTokenCount() {
|
126 |
+
return this.activeTokens.length;
|
127 |
+
}
|
128 |
+
|
129 |
+
// 获取所有活跃 token
|
130 |
+
getActiveTokens() {
|
131 |
+
return [...this.activeTokens];
|
132 |
+
}
|
133 |
+
}
|
134 |
+
|
135 |
+
const tokenManager = new AuthTokenManager();
|
136 |
+
await initialization();
|
137 |
+
|
138 |
+
|
139 |
// 工具类
|
140 |
class Utils {
|
141 |
static generateRandomString(length, charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
|
|
|
143 |
.map(() => charset[Math.floor(Math.random() * charset.length)])
|
144 |
.join('');
|
145 |
}
|
146 |
+
static getRandomID(size) {
|
147 |
+
const customDict = '0123456789';
|
148 |
+
return Array(size).fill(null)
|
149 |
+
.map(() => customDict[(Math.random() * customDict.length) | 0])
|
150 |
+
.join('');
|
151 |
+
}
|
152 |
static createAuthHeaders() {
|
153 |
return {
|
154 |
...DEFAULT_HEADERS,
|
155 |
+
'x-csrf-token': tokenManager.getTokenByIndex(CONFIG.SIGNATURE_INDEX).split(';')[1].split('=')[1],
|
156 |
+
'cookie': `${tokenManager.getTokenByIndex(CONFIG.SIGNATURE_INDEX)}`
|
157 |
};
|
158 |
}
|
159 |
|
|
|
185 |
const data = await Utils.handleApiResponse(response, '创建会话失败!');
|
186 |
return data.data.create_grok_conversation.conversation_id;
|
187 |
}
|
188 |
+
// static generateNewId() {
|
189 |
+
// this.conversationId = `1875${Utils.getRandomID(15)}`;
|
190 |
+
// return this.conversationId;
|
191 |
+
// }
|
192 |
|
193 |
static async deleteConversation(conversationId) {
|
194 |
if (!conversationId) return;
|
|
|
504 |
let buffer = '';
|
505 |
let fullResponse = '';
|
506 |
let imageUrl = null;
|
|
|
507 |
try {
|
508 |
for await (const chunk of reader) {
|
509 |
const lines = (buffer + chunk.toString()).split('\n');
|
|
|
521 |
if (imageUrl) {
|
522 |
await this.sendImageResponse(imageUrl, model, res);
|
523 |
} else {
|
524 |
+
if (fullResponse.includes("You've reached your limit of 15 Grok")) {
|
525 |
+
throw new Error('You have reached your limit of 15 Grok');
|
526 |
+
}
|
527 |
const responseData = MessageProcessor.createChatResponse(fullResponse, model);
|
528 |
res.json(responseData);
|
529 |
}
|
|
|
604 |
app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
|
605 |
|
606 |
// API 路由
|
607 |
+
app.get('/v1/models', (req, res) => {
|
608 |
res.json({
|
609 |
object: "list",
|
610 |
data: Object.keys(CONFIG.MODELS).map(model => ({
|
|
|
615 |
}))
|
616 |
});
|
617 |
});
|
618 |
+
app.post('/v1/chat/completions', async (req, res) => {
|
|
|
619 |
try {
|
620 |
const authToken = req.headers.authorization?.replace('Bearer ', '');
|
621 |
if (authToken !== CONFIG.API.API_KEY) {
|
622 |
return res.status(401).json({ error: 'Unauthorized' });
|
623 |
}
|
624 |
|
625 |
+
while (tokenManager.getTokenCount() > 0) {
|
626 |
+
const grokClient = new TwitterGrokApiClient(req.body.model);
|
627 |
+
const requestPayload = await grokClient.prepareChatRequest(req.body);
|
628 |
+
Logger.info(`当前令牌索引: ${CONFIG.SIGNATURE_INDEX}`, 'Server');
|
629 |
+
const response = await fetch(CONFIG.API.ENDPOINTS.CHAT, {
|
630 |
+
method: 'POST',
|
631 |
+
headers: Utils.createAuthHeaders(),
|
632 |
+
body: JSON.stringify(requestPayload)
|
633 |
+
});
|
634 |
+
|
635 |
+
if (response.ok) {
|
636 |
+
Logger.info(`请求成功`, 'Server');
|
637 |
+
CONFIG.SIGNATURE_INDEX = (CONFIG.SIGNATURE_INDEX + 1) % tokenManager.getTokenCount();
|
638 |
+
Logger.info(`当前剩余可用令牌数: ${tokenManager.getTokenCount()}`, 'Server');
|
639 |
+
try {
|
640 |
+
await (req.body.stream
|
641 |
+
? ResponseHandler.handleStreamResponse(response, req.body.model, res)
|
642 |
+
: ResponseHandler.handleNormalResponse(response, req.body.model, res));
|
643 |
+
return; // 成功后直接返回
|
644 |
+
} catch (error) {
|
645 |
+
tokenManager.removeTokenByIndex(CONFIG.SIGNATURE_INDEX);
|
646 |
+
if (!tokenManager.isRecoveryProcess) {
|
647 |
+
tokenManager.startTokenRecoveryProcess();
|
648 |
+
}
|
649 |
+
Logger.warn(`当前令牌失效,已移除令牌,剩余令牌数: ${tokenManager.getTokenCount()}`, 'Server');
|
650 |
+
// 更新签名索引
|
651 |
+
CONFIG.SIGNATURE_INDEX = (CONFIG.SIGNATURE_INDEX + 1) % tokenManager.getTokenCount();
|
652 |
+
}
|
653 |
+
} else {
|
654 |
+
// 处理429错误
|
655 |
+
if (response.status === 429) {
|
656 |
+
tokenManager.removeTokenByIndex(CONFIG.SIGNATURE_INDEX);
|
657 |
+
if (!tokenManager.isRecoveryProcess) {
|
658 |
+
tokenManager.startTokenRecoveryProcess();
|
659 |
+
}
|
660 |
+
Logger.warn(`当前令牌失效,已移除令牌,剩余令牌数: ${tokenManager.getTokenCount()}`, 'Server');
|
661 |
+
// 更新签名索引
|
662 |
+
CONFIG.SIGNATURE_INDEX = (CONFIG.SIGNATURE_INDEX + 1) % tokenManager.getTokenCount();
|
663 |
+
Logger.warn(`请求被限流,剩余重试次数: ${tokenManager.getTokenCount()}`, 'ChatAPI');
|
664 |
+
} else {
|
665 |
+
// 非429错误直接抛出
|
666 |
+
throw new Error(`上游服务请求失败! status: ${response.status}`);
|
667 |
+
}
|
668 |
+
}
|
669 |
}
|
670 |
|
671 |
+
// 如果重试次数用完仍然是429
|
672 |
+
throw new Error('所有令牌都已耗尽,请求被限流');
|
|
|
673 |
|
674 |
} catch (error) {
|
675 |
Logger.error('Chat Completions Request Error', error, 'ChatAPI');
|
|
|
681 |
code: error.code || null
|
682 |
}
|
683 |
});
|
|
|
|
|
|
|
|
|
684 |
}
|
685 |
});
|
686 |
|
687 |
// 404 处理
|
688 |
app.use((req, res) => {
|
689 |
+
res.status(404).json({ error: '请求路径不存在' });
|
690 |
});
|
691 |
|
692 |
// 启动服务器
|