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,80 @@ const DEFAULT_HEADERS = {
|
|
58 |
'priority': 'u=1, i'
|
59 |
};
|
60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
// 工具类
|
62 |
class Utils {
|
63 |
static generateRandomString(length, charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
|
@@ -65,12 +141,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 |
|
@@ -202,14 +283,45 @@ class TwitterGrokApiClient {
|
|
202 |
}
|
203 |
|
204 |
async transformMessages(messages) {
|
|
|
|
|
|
|
|
|
205 |
const processedMessages = [];
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
211 |
}
|
212 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
return processedMessages;
|
214 |
}
|
215 |
|
@@ -228,7 +340,7 @@ class TwitterGrokApiClient {
|
|
228 |
|
229 |
return {
|
230 |
message,
|
231 |
-
sender: role === '
|
232 |
...(role === 'user' && { fileAttachments })
|
233 |
};
|
234 |
}
|
@@ -386,7 +498,6 @@ class ResponseHandler {
|
|
386 |
let buffer = '';
|
387 |
let fullResponse = '';
|
388 |
let imageUrl = null;
|
389 |
-
|
390 |
try {
|
391 |
for await (const chunk of reader) {
|
392 |
const lines = (buffer + chunk.toString()).split('\n');
|
@@ -404,6 +515,9 @@ class ResponseHandler {
|
|
404 |
if (imageUrl) {
|
405 |
await this.sendImageResponse(imageUrl, model, res);
|
406 |
} else {
|
|
|
|
|
|
|
407 |
const responseData = MessageProcessor.createChatResponse(fullResponse, model);
|
408 |
res.json(responseData);
|
409 |
}
|
@@ -495,7 +609,6 @@ app.get('/hf/v1/models', (req, res) => {
|
|
495 |
}))
|
496 |
});
|
497 |
});
|
498 |
-
|
499 |
app.post('/hf/v1/chat/completions', async (req, res) => {
|
500 |
try {
|
501 |
const authToken = req.headers.authorization?.replace('Bearer ', '');
|
@@ -503,22 +616,54 @@ app.post('/hf/v1/chat/completions', async (req, res) => {
|
|
503 |
return res.status(401).json({ error: 'Unauthorized' });
|
504 |
}
|
505 |
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
517 |
}
|
518 |
|
519 |
-
|
520 |
-
|
521 |
-
: ResponseHandler.handleNormalResponse(response, req.body.model, res));
|
522 |
|
523 |
} catch (error) {
|
524 |
Logger.error('Chat Completions Request Error', error, 'ChatAPI');
|
@@ -530,10 +675,6 @@ app.post('/hf/v1/chat/completions', async (req, res) => {
|
|
530 |
code: error.code || null
|
531 |
}
|
532 |
});
|
533 |
-
} finally {
|
534 |
-
if (req.body.conversationId) {
|
535 |
-
await ConversationManager.deleteConversation(req.body.conversationId);
|
536 |
-
}
|
537 |
}
|
538 |
});
|
539 |
|
@@ -545,4 +686,5 @@ app.use((req, res) => {
|
|
545 |
// 启动服务器
|
546 |
app.listen(CONFIG.SERVER.PORT, () => {
|
547 |
Logger.info(`服务器运行在端口 ${CONFIG.SERVER.PORT}`, 'Server');
|
548 |
-
});
|
|
|
|
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 |
+
for (const [token, expiredTime] of this.expiredTokens.entries()) {
|
113 |
+
if (now - expiredTime >= 2 * 60 * 60 * 1000) {
|
114 |
+
this.activeTokens.push(token);
|
115 |
+
this.expiredTokens.delete(token);
|
116 |
+
console.log(`Token ${token} recovered`);
|
117 |
+
}
|
118 |
+
}
|
119 |
+
}, 2 * 60 * 60 * 1000);
|
120 |
+
}
|
121 |
+
|
122 |
+
// 获取 token 总数
|
123 |
+
getTokenCount() {
|
124 |
+
return this.activeTokens.length;
|
125 |
+
}
|
126 |
+
|
127 |
+
// 获取所有活跃 token
|
128 |
+
getActiveTokens() {
|
129 |
+
return [...this.activeTokens];
|
130 |
+
}
|
131 |
+
}
|
132 |
+
|
133 |
+
const tokenManager = new AuthTokenManager();
|
134 |
+
await initialization();
|
135 |
+
|
136 |
+
|
137 |
// 工具类
|
138 |
class Utils {
|
139 |
static generateRandomString(length, charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
|
|
|
141 |
.map(() => charset[Math.floor(Math.random() * charset.length)])
|
142 |
.join('');
|
143 |
}
|
144 |
+
static getRandomID(size) {
|
145 |
+
const customDict = '0123456789';
|
146 |
+
return Array(size).fill(null)
|
147 |
+
.map(() => customDict[(Math.random() * customDict.length) | 0])
|
148 |
+
.join('');
|
149 |
+
}
|
150 |
static createAuthHeaders() {
|
151 |
return {
|
152 |
...DEFAULT_HEADERS,
|
153 |
+
'x-csrf-token': tokenManager.getTokenByIndex(CONFIG.SIGNATURE_INDEX).split(';')[1].split('=')[1],
|
154 |
+
'cookie': `${tokenManager.getTokenByIndex(CONFIG.SIGNATURE_INDEX)}`
|
155 |
};
|
156 |
}
|
157 |
|
|
|
283 |
}
|
284 |
|
285 |
async transformMessages(messages) {
|
286 |
+
if (messages[0].role === 'assistant') {
|
287 |
+
throw new Error('ai不能是第一个消息');
|
288 |
+
}
|
289 |
+
|
290 |
const processedMessages = [];
|
291 |
+
let currentMessage = null;
|
292 |
+
|
293 |
+
for (const msg of messages) {
|
294 |
+
const normalizedMsg = msg.role === 'system' ? { ...msg, role: 'user' } : msg;
|
295 |
+
|
296 |
+
if (!currentMessage || currentMessage.role !== normalizedMsg.role) {
|
297 |
+
if (currentMessage) {
|
298 |
+
const processedContent = await this.processMessageContent(
|
299 |
+
currentMessage,
|
300 |
+
processedMessages.length >= messages.length - 2
|
301 |
+
);
|
302 |
+
if (processedContent) {
|
303 |
+
processedMessages.push(processedContent);
|
304 |
+
}
|
305 |
+
}
|
306 |
+
currentMessage = normalizedMsg;
|
307 |
+
} else {
|
308 |
+
currentMessage.content = typeof currentMessage.content === 'string' && typeof normalizedMsg.content === 'string'
|
309 |
+
? `${currentMessage.content}\n${normalizedMsg.content}`
|
310 |
+
: normalizedMsg.content;
|
311 |
}
|
312 |
}
|
313 |
+
|
314 |
+
// 处理最后一个消息
|
315 |
+
if (currentMessage) {
|
316 |
+
const processedContent = await this.processMessageContent(
|
317 |
+
currentMessage,
|
318 |
+
true
|
319 |
+
);
|
320 |
+
if (processedContent) {
|
321 |
+
processedMessages.push(processedContent);
|
322 |
+
}
|
323 |
+
}
|
324 |
+
|
325 |
return processedMessages;
|
326 |
}
|
327 |
|
|
|
340 |
|
341 |
return {
|
342 |
message,
|
343 |
+
sender: role === 'assistant' ? 2 : 1,
|
344 |
...(role === 'user' && { fileAttachments })
|
345 |
};
|
346 |
}
|
|
|
498 |
let buffer = '';
|
499 |
let fullResponse = '';
|
500 |
let imageUrl = null;
|
|
|
501 |
try {
|
502 |
for await (const chunk of reader) {
|
503 |
const lines = (buffer + chunk.toString()).split('\n');
|
|
|
515 |
if (imageUrl) {
|
516 |
await this.sendImageResponse(imageUrl, model, res);
|
517 |
} else {
|
518 |
+
if (fullResponse.includes("You've reached your limit of 15 Grok")) {
|
519 |
+
throw new Error('You have reached your limit of 15 Grok');
|
520 |
+
}
|
521 |
const responseData = MessageProcessor.createChatResponse(fullResponse, model);
|
522 |
res.json(responseData);
|
523 |
}
|
|
|
609 |
}))
|
610 |
});
|
611 |
});
|
|
|
612 |
app.post('/hf/v1/chat/completions', async (req, res) => {
|
613 |
try {
|
614 |
const authToken = req.headers.authorization?.replace('Bearer ', '');
|
|
|
616 |
return res.status(401).json({ error: 'Unauthorized' });
|
617 |
}
|
618 |
|
619 |
+
while (tokenManager.getTokenCount() > 0) {
|
620 |
+
const grokClient = new TwitterGrokApiClient(req.body.model);
|
621 |
+
const requestPayload = await grokClient.prepareChatRequest(req.body);
|
622 |
+
Logger.info(`当前令牌索引: ${CONFIG.SIGNATURE_INDEX}`, 'Server');
|
623 |
+
const response = await fetch(CONFIG.API.ENDPOINTS.CHAT, {
|
624 |
+
method: 'POST',
|
625 |
+
headers: Utils.createAuthHeaders(),
|
626 |
+
body: JSON.stringify(requestPayload)
|
627 |
+
});
|
628 |
+
|
629 |
+
if (response.ok) {
|
630 |
+
Logger.info(`请求成功`, 'Server');
|
631 |
+
CONFIG.SIGNATURE_INDEX = (CONFIG.SIGNATURE_INDEX + 1) % tokenManager.getTokenCount();
|
632 |
+
Logger.info(`当前剩余可用令牌数: ${tokenManager.getTokenCount()}`, 'Server');
|
633 |
+
try {
|
634 |
+
await (req.body.stream
|
635 |
+
? ResponseHandler.handleStreamResponse(response, req.body.model, res)
|
636 |
+
: ResponseHandler.handleNormalResponse(response, req.body.model, res));
|
637 |
+
return; // 成功后直接返回
|
638 |
+
} catch (error) {
|
639 |
+
tokenManager.removeTokenByIndex(CONFIG.SIGNATURE_INDEX);
|
640 |
+
if (!tokenManager.isRecoveryProcess) {
|
641 |
+
tokenManager.startTokenRecoveryProcess();
|
642 |
+
}
|
643 |
+
Logger.warn(`当前令牌失效,已移除令牌,剩余令牌数: ${tokenManager.getTokenCount()}`, 'Server');
|
644 |
+
// 更新签名索引
|
645 |
+
CONFIG.SIGNATURE_INDEX = (CONFIG.SIGNATURE_INDEX + 1) % tokenManager.getTokenCount();
|
646 |
+
}
|
647 |
+
} else {
|
648 |
+
// 处理429错误
|
649 |
+
if (response.status === 429) {
|
650 |
+
tokenManager.removeTokenByIndex(CONFIG.SIGNATURE_INDEX);
|
651 |
+
if (!tokenManager.isRecoveryProcess) {
|
652 |
+
tokenManager.startTokenRecoveryProcess();
|
653 |
+
}
|
654 |
+
Logger.warn(`当前令牌失效,已移除令牌,剩余令牌数: ${tokenManager.getTokenCount()}`, 'Server');
|
655 |
+
// 更新签名索引
|
656 |
+
CONFIG.SIGNATURE_INDEX = (CONFIG.SIGNATURE_INDEX + 1) % tokenManager.getTokenCount();
|
657 |
+
Logger.warn(`请求被限流,剩余重试次数: ${tokenManager.getTokenCount()}`, 'ChatAPI');
|
658 |
+
} else {
|
659 |
+
// 非429错误直接抛出
|
660 |
+
throw new Error(`上游服务请求失败! status: ${response.status}`);
|
661 |
+
}
|
662 |
+
}
|
663 |
}
|
664 |
|
665 |
+
// 如果重试次数用完仍然是429
|
666 |
+
throw new Error('所有令牌都已耗尽,请求被限流');
|
|
|
667 |
|
668 |
} catch (error) {
|
669 |
Logger.error('Chat Completions Request Error', error, 'ChatAPI');
|
|
|
675 |
code: error.code || null
|
676 |
}
|
677 |
});
|
|
|
|
|
|
|
|
|
678 |
}
|
679 |
});
|
680 |
|
|
|
686 |
// 启动服务器
|
687 |
app.listen(CONFIG.SERVER.PORT, () => {
|
688 |
Logger.info(`服务器运行在端口 ${CONFIG.SERVER.PORT}`, 'Server');
|
689 |
+
});
|
690 |
+
|