letterm commited on
Commit
935ec97
·
verified ·
1 Parent(s): 4a429d6

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +543 -543
index.js CHANGED
@@ -1,544 +1,544 @@
1
- import express from 'express';
2
- import FormData from 'form-data';
3
- import { v4 as uuidv4 } from 'uuid';
4
- import fetch from 'node-fetch';
5
- 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 || 25526,
16
- BODY_LIMIT: '5mb',
17
- CORS_OPTIONS: {
18
- origin: '*',
19
- methods: ['GET', 'POST', 'OPTIONS'],
20
- allowedHeaders: ['Content-Type', 'Authorization'],
21
- credentials: true
22
- }
23
- },
24
- API: {
25
- API_KEY: process.env.API_KEY || "sk-123456",
26
- AUTH_TOKEN: process.env.AUTH_TOKEN,
27
- CT0: process.env.CT0,
28
- ENDPOINTS: {
29
- CHAT: 'https://grok.x.com/2/grok/add_response.json',
30
- CREATE_CONVERSATION: 'https://x.com/i/api/graphql/vvC5uy7pWWHXS2aDi1FZeA/CreateGrokConversation',
31
- DELETE_CONVERSATION: 'https://x.com/i/api/graphql/TlKHSWVMVeaa-i7dqQqFQA/ConversationItem_DeleteConversationMutation',
32
- UPLOAD_IMAGE: 'https://x.com/i/api/2/grok/attachment.json'
33
- }
34
- },
35
- MODELS: {
36
- "grok-3": "grok-3",
37
- "grok-3-deepsearch": "grok-3",
38
- "grok-3-reasoning": "grok-3",
39
- }
40
- };
41
-
42
- // HTTP 请求头配置
43
- const DEFAULT_HEADERS = {
44
- 'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
45
- 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
46
- 'sec-ch-ua-mobile': '?0',
47
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
48
- 'accept': '*/*',
49
- 'content-type': 'text/plain;charset=UTF-8',
50
- 'origin': 'https://x.com',
51
- 'sec-fetch-site': 'same-site',
52
- 'sec-fetch-mode': 'cors',
53
- 'accept-encoding': 'gzip, deflate, br, zstd',
54
- 'accept-language': 'zh-CN,zh;q=0.9',
55
- 'priority': 'u=1, i'
56
- };
57
-
58
- // 工具类
59
- class Utils {
60
- static generateRandomString(length, charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
61
- return Array(length).fill(null)
62
- .map(() => charset[Math.floor(Math.random() * charset.length)])
63
- .join('');
64
- }
65
-
66
- static createAuthHeaders() {
67
- return {
68
- ...DEFAULT_HEADERS,
69
- 'x-csrf-token': CONFIG.API.CT0,
70
- 'cookie': `auth_token=${CONFIG.API.AUTH_TOKEN};ct0=${CONFIG.API.CT0}`
71
- };
72
- }
73
-
74
- static getImageMimeType(base64String) {
75
- const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
76
- return matches ? matches[1] : 'image/jpeg';
77
- }
78
-
79
- static async handleApiResponse(response, errorMessage) {
80
- if (!response.ok) {
81
- throw new Error(`${errorMessage} Status: ${response.status}`);
82
- }
83
- return await response.json();
84
- }
85
- }
86
-
87
- // 会话管理类
88
- class ConversationManager {
89
- static async generateNewId() {
90
- const response = await fetch(CONFIG.API.ENDPOINTS.CREATE_CONVERSATION, {
91
- method: 'POST',
92
- headers: Utils.createAuthHeaders(),
93
- body: JSON.stringify({
94
- variables: {},
95
- queryId: "vvC5uy7pWWHXS2aDi1FZeA"
96
- })
97
- });
98
-
99
- const data = await Utils.handleApiResponse(response, '创建会话失败!');
100
- return data.data.create_grok_conversation.conversation_id;
101
- }
102
-
103
- static async deleteConversation(conversationId) {
104
- if (!conversationId) return;
105
-
106
- await fetch(CONFIG.API.ENDPOINTS.DELETE_CONVERSATION, {
107
- method: 'POST',
108
- headers: Utils.createAuthHeaders(),
109
- body: JSON.stringify({
110
- variables: { conversationId },
111
- queryId: "TlKHSWVMVeaa-i7dqQqFQA"
112
- })
113
- });
114
- }
115
- }
116
-
117
- // 消息处理类
118
- class MessageProcessor {
119
- static createChatResponse(message, model, isStream = false) {
120
- const baseResponse = {
121
- id: `chatcmpl-${uuidv4()}`,
122
- created: Math.floor(Date.now() / 1000),
123
- model: model
124
- };
125
-
126
- if (isStream) {
127
- return {
128
- ...baseResponse,
129
- object: 'chat.completion.chunk',
130
- choices: [{
131
- index: 0,
132
- delta: { content: message }
133
- }]
134
- };
135
- }
136
-
137
- return {
138
- ...baseResponse,
139
- object: 'chat.completion',
140
- choices: [{
141
- index: 0,
142
- message: {
143
- role: 'assistant',
144
- content: message
145
- },
146
- finish_reason: 'stop'
147
- }],
148
- usage: null
149
- };
150
- }
151
-
152
- static processMessageContent(content) {
153
- if (typeof content === 'string') return content;
154
- if (Array.isArray(content)) {
155
- if (content.some(item => item.type === 'image_url')) return null;
156
- return content
157
- .filter(item => item.type === 'text')
158
- .map(item => item.text)
159
- .join('\n');
160
- }
161
- if (typeof content === 'object') return content.text || null;
162
- return null;
163
- }
164
- }
165
-
166
- // Grok API 客户端类
167
- class TwitterGrokApiClient {
168
- constructor(modelId) {
169
- if (!CONFIG.MODELS[modelId]) {
170
- throw new Error(`不支持的模型: ${modelId}`);
171
- }
172
- this.modelId = CONFIG.MODELS[modelId];
173
- this.modelType = {
174
- isDeepSearch: modelId === 'grok-3-deepsearch',
175
- isReasoning: modelId === 'grok-3-reasoning'
176
- };
177
- }
178
-
179
- async uploadImage(imageData) {
180
- const formData = new FormData();
181
- const imageBuffer = Buffer.from(imageData.split(',')[1], 'base64');
182
- const mimeType = Utils.getImageMimeType(imageData);
183
-
184
- formData.append('photo', imageBuffer, {
185
- filename: 'image.png',
186
- contentType: mimeType
187
- });
188
-
189
- const response = await fetch(CONFIG.API.ENDPOINTS.UPLOAD_IMAGE, {
190
- method: 'POST',
191
- headers: {
192
- ...Utils.createAuthHeaders(),
193
- ...formData.getHeaders()
194
- },
195
- body: formData
196
- });
197
-
198
- return await Utils.handleApiResponse(response, '图片上传失败');
199
- }
200
-
201
- async transformMessages(messages) {
202
- const processedMessages = [];
203
- for (let i = 0; i < messages.length; i++) {
204
- const isLastTwoMessages = i >= messages.length - 2;
205
- const content = await this.processMessageContent(messages[i], isLastTwoMessages);
206
- if (content) {
207
- processedMessages.push(content);
208
- }
209
- }
210
- return processedMessages;
211
- }
212
-
213
- async processMessageContent(msg, isLastTwoMessages) {
214
- const { role, content } = msg;
215
- let message = '';
216
- let fileAttachments = [];
217
-
218
- if (typeof content === 'string') {
219
- message = content;
220
- } else if (Array.isArray(content) || typeof content === 'object') {
221
- const { text, imageAttachments } = await this.processComplexContent(content, isLastTwoMessages);
222
- message = text;
223
- fileAttachments = imageAttachments;
224
- }
225
-
226
- return {
227
- message,
228
- sender: role === 'user' ? 1 : 2,
229
- ...(role === 'user' && { fileAttachments })
230
- };
231
- }
232
-
233
- async processComplexContent(content, isLastTwoMessages) {
234
- let text = '';
235
- let imageAttachments = [];
236
-
237
- const processItem = async (item) => {
238
- if (item.type === 'text') {
239
- text += item.text;
240
- } else if (item.type === 'image_url' && item.image_url.url.includes('data:image')) {
241
- if (isLastTwoMessages) {
242
- const uploadResult = await this.uploadImage(item.image_url.url);
243
- if (Array.isArray(uploadResult)) {
244
- imageAttachments.push(...uploadResult);
245
- }
246
- } else {
247
- text += '[图片]';
248
- }
249
- }
250
- };
251
-
252
- if (Array.isArray(content)) {
253
- await Promise.all(content.map(processItem));
254
- } else {
255
- await processItem(content);
256
- }
257
-
258
- return { text, imageAttachments };
259
- }
260
-
261
- async prepareChatRequest(request) {
262
- const responses = await this.transformMessages(request.messages);
263
- const conversationId = await ConversationManager.generateNewId();
264
-
265
- return {
266
- responses,
267
- systemPromptName: "",
268
- grokModelOptionId: this.modelId,
269
- conversationId,
270
- returnSearchResults: this.modelType.isReasoning,
271
- returnCitations: this.modelType.isReasoning,
272
- promptMetadata: {
273
- promptSource: "NATURAL",
274
- action: "INPUT"
275
- },
276
- imageGenerationCount: 1,
277
- requestFeatures: {
278
- eagerTweets: false,
279
- serverHistory: false
280
- },
281
- enableCustomization: true,
282
- enableSideBySide: false,
283
- toolOverrides: {},
284
- isDeepsearch: this.modelType.isDeepSearch,
285
- isReasoning: this.modelType.isReasoning
286
- };
287
- }
288
- }
289
-
290
- // 响应处理类
291
- class ResponseHandler {
292
- static async handleStreamResponse(response, model, res) {
293
- res.setHeader('Content-Type', 'text/event-stream');
294
- res.setHeader('Cache-Control', 'no-cache');
295
- res.setHeader('Connection', 'keep-alive');
296
-
297
- const reader = response.body;
298
- let buffer = '';
299
- let isImgGen = false;
300
- let isThinking = false;
301
-
302
- try {
303
- for await (const chunk of reader) {
304
- const lines = (buffer + chunk.toString()).split('\n');
305
- buffer = lines.pop() || '';
306
-
307
- for (const line of lines) {
308
- if (!line.trim()) continue;
309
- await this.processStreamLine(JSON.parse(line), model, res, isImgGen, isThinking);
310
- }
311
- }
312
-
313
- res.write('data: [DONE]\n\n');
314
- res.end();
315
- } catch (error) {
316
- Logger.error('Stream response error:', error, 'ChatAPI');
317
- throw error;
318
- }
319
- }
320
-
321
- static async processStreamLine(jsonData, model, res, isImgGen, isThinking) {
322
- if (jsonData.result?.doImgGen) {
323
- isImgGen = true;
324
- return;
325
- }
326
-
327
- if (isImgGen && jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
328
- await this.handleImageGeneration(jsonData, model, res);
329
- return;
330
- }
331
-
332
- if (!isImgGen && jsonData.result?.message) {
333
- await this.handleTextMessage(jsonData, model, res, isThinking);
334
- }
335
- }
336
-
337
- static async handleImageGeneration(jsonData, model, res) {
338
- const imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
339
- const imageResponse = await fetch(imageUrl, {
340
- method: 'GET',
341
- headers: Utils.createAuthHeaders()
342
- });
343
-
344
- if (!imageResponse.ok) {
345
- throw new Error(`Image request failed: ${imageResponse.status}`);
346
- }
347
-
348
- const imageBuffer = await imageResponse.arrayBuffer();
349
- const base64Image = Buffer.from(imageBuffer).toString('base64');
350
- const imageContentType = imageResponse.headers.get('content-type');
351
- const message = `![image](data:${imageContentType};base64,${base64Image})`;
352
-
353
- const responseData = MessageProcessor.createChatResponse(message, model, true);
354
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
355
- }
356
-
357
- static async handleTextMessage(jsonData, model, res, isThinking) {
358
- let message = jsonData.result.message;
359
-
360
- switch (model) {
361
- case "grok-3-reasoning":
362
- if (!isThinking && jsonData.result?.isThinking) {
363
- message = "<think>" + message;
364
- isThinking = true;
365
- } else if (isThinking && !jsonData.result?.isThinking) {
366
- message = "</think>" + message;
367
- isThinking = false;
368
- }
369
- break;
370
- case "grok-3-deepsearch":
371
- if (jsonData.result?.messageTag !== "final") return;
372
- break;
373
- }
374
-
375
- const responseData = MessageProcessor.createChatResponse(message, model, true);
376
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
377
- }
378
-
379
- static async handleNormalResponse(response, model, res) {
380
- const reader = response.body;
381
- let buffer = '';
382
- let fullResponse = '';
383
- let imageUrl = null;
384
- let isThinking = false;
385
-
386
- try {
387
- for await (const chunk of reader) {
388
- const lines = (buffer + chunk.toString()).split('\n');
389
- buffer = lines.pop() || '';
390
-
391
- for (const line of lines) {
392
- if (!line.trim()) continue;
393
- const result = await this.processNormalLine(JSON.parse(line), model, isThinking);
394
- fullResponse += result.text || '';
395
- imageUrl = result.imageUrl || imageUrl;
396
- isThinking = result.isThinking;
397
- }
398
- }
399
-
400
- if (imageUrl) {
401
- await this.sendImageResponse(imageUrl, model, res);
402
- } else {
403
- const responseData = MessageProcessor.createChatResponse(fullResponse, model);
404
- res.json(responseData);
405
- }
406
- } catch (error) {
407
- Logger.error('Normal response error:', error, 'ChatAPI');
408
- throw error;
409
- }
410
- }
411
-
412
- static async processNormalLine(jsonData, model, isThinking) {
413
- let result = { text: '', imageUrl: null, isThinking };
414
-
415
- if (jsonData.result?.message) {
416
- switch (model) {
417
- case "grok-3-reasoning":
418
- result = this.processReasoningMessage(jsonData, isThinking);
419
- break;
420
- case "grok-3-deepsearch":
421
- if (jsonData.result?.messageTag === "final") {
422
- result.text = jsonData.result.message;
423
- }
424
- break;
425
- default:
426
- result.text = jsonData.result.message;
427
- }
428
- }
429
-
430
- if (jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
431
- result.imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
432
- }
433
-
434
- return result;
435
- }
436
-
437
- static processReasoningMessage(jsonData, isThinking) {
438
- let result = { text: '', isThinking };
439
-
440
- if (jsonData.result?.isThinking && !isThinking) {
441
- result.text = "<think>" + jsonData.result.message;
442
- result.isThinking = true;
443
- } else if (isThinking && !jsonData.result?.isThinking) {
444
- result.text = "</think>" + jsonData.result.message;
445
- result.isThinking = false;
446
- } else {
447
- result.text = jsonData.result.message;
448
- }
449
-
450
- return result;
451
- }
452
-
453
- static async sendImageResponse(imageUrl, model, res) {
454
- const response = await fetch(imageUrl, {
455
- method: 'GET',
456
- headers: Utils.createAuthHeaders()
457
- });
458
-
459
- if (!response.ok) {
460
- throw new Error(`Image request failed: ${response.status}`);
461
- }
462
-
463
- const imageBuffer = await response.arrayBuffer();
464
- const base64Image = Buffer.from(imageBuffer).toString('base64');
465
- const imageContentType = response.headers.get('content-type');
466
-
467
- const responseData = MessageProcessor.createChatResponse(
468
- `![image](data:${imageContentType};base64,${base64Image})`,
469
- model
470
- );
471
- res.json(responseData);
472
- }
473
- }
474
-
475
- // Express 应用配置
476
- const app = express();
477
- app.use(Logger.requestLogger);
478
- app.use(cors(CONFIG.SERVER.CORS_OPTIONS));
479
- app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
480
- app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
481
-
482
- // API 路由
483
- app.get('/v1/models', (req, res) => {
484
- res.json({
485
- object: "list",
486
- data: Object.keys(CONFIG.MODELS).map(model => ({
487
- id: model,
488
- object: "model",
489
- created: Math.floor(Date.now() / 1000),
490
- owned_by: "xgrok",
491
- }))
492
- });
493
- });
494
-
495
- app.post('/v1/chat/completions', async (req, res) => {
496
- try {
497
- const authToken = req.headers.authorization?.replace('Bearer ', '');
498
- if (authToken !== CONFIG.API.API_KEY) {
499
- return res.status(401).json({ error: 'Unauthorized' });
500
- }
501
-
502
- const grokClient = new TwitterGrokApiClient(req.body.model);
503
- const requestPayload = await grokClient.prepareChatRequest(req.body);
504
-
505
- const response = await fetch(CONFIG.API.ENDPOINTS.CHAT, {
506
- method: 'POST',
507
- headers: Utils.createAuthHeaders(),
508
- body: JSON.stringify(requestPayload)
509
- });
510
-
511
- if (!response.ok) {
512
- throw new Error(`上游服务请求失败! status: ${response.status}`);
513
- }
514
-
515
- await (req.body.stream
516
- ? ResponseHandler.handleStreamResponse(response, req.body.model, res)
517
- : ResponseHandler.handleNormalResponse(response, req.body.model, res));
518
-
519
- } catch (error) {
520
- Logger.error('Chat Completions Request Error', error, 'ChatAPI');
521
- res.status(500).json({
522
- error: {
523
- message: error.message,
524
- type: 'server_error',
525
- param: null,
526
- code: error.code || null
527
- }
528
- });
529
- } finally {
530
- if (req.body.conversationId) {
531
- await ConversationManager.deleteConversation(req.body.conversationId);
532
- }
533
- }
534
- });
535
-
536
- // 404 处理
537
- app.use((req, res) => {
538
- res.status(404).json({ error: '请求路径不存在' });
539
- });
540
-
541
- // 启动服务器
542
- app.listen(CONFIG.SERVER.PORT, () => {
543
- Logger.info(`服务器运行在端口 ${CONFIG.SERVER.PORT}`, 'Server');
544
  });
 
1
+ import express from 'express';
2
+ import FormData from 'form-data';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import fetch from 'node-fetch';
5
+ 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 || 25526,
16
+ BODY_LIMIT: '5mb',
17
+ CORS_OPTIONS: {
18
+ origin: '*',
19
+ methods: ['GET', 'POST', 'OPTIONS'],
20
+ allowedHeaders: ['Content-Type', 'Authorization'],
21
+ credentials: true
22
+ }
23
+ },
24
+ API: {
25
+ API_KEY: process.env.API_KEY || "sk-123456",
26
+ AUTH_TOKEN: process.env.AUTH_TOKEN,
27
+ CT0: process.env.CT0,
28
+ ENDPOINTS: {
29
+ CHAT: 'https://grok.x.com/2/grok/add_response.json',
30
+ CREATE_CONVERSATION: 'https://x.com/i/api/graphql/vvC5uy7pWWHXS2aDi1FZeA/CreateGrokConversation',
31
+ DELETE_CONVERSATION: 'https://x.com/i/api/graphql/TlKHSWVMVeaa-i7dqQqFQA/ConversationItem_DeleteConversationMutation',
32
+ UPLOAD_IMAGE: 'https://x.com/i/api/2/grok/attachment.json'
33
+ }
34
+ },
35
+ MODELS: {
36
+ "grok-3": "grok-3",
37
+ "grok-3-deepsearch": "grok-3",
38
+ "grok-3-reasoning": "grok-3",
39
+ }
40
+ };
41
+
42
+ // HTTP 请求头配置
43
+ const DEFAULT_HEADERS = {
44
+ 'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
45
+ 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
46
+ 'sec-ch-ua-mobile': '?0',
47
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
48
+ 'accept': '*/*',
49
+ 'content-type': 'text/plain;charset=UTF-8',
50
+ 'origin': 'https://x.com',
51
+ 'sec-fetch-site': 'same-site',
52
+ 'sec-fetch-mode': 'cors',
53
+ 'accept-encoding': 'gzip, deflate, br, zstd',
54
+ 'accept-language': 'zh-CN,zh;q=0.9',
55
+ 'priority': 'u=1, i'
56
+ };
57
+
58
+ // 工具类
59
+ class Utils {
60
+ static generateRandomString(length, charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
61
+ return Array(length).fill(null)
62
+ .map(() => charset[Math.floor(Math.random() * charset.length)])
63
+ .join('');
64
+ }
65
+
66
+ static createAuthHeaders() {
67
+ return {
68
+ ...DEFAULT_HEADERS,
69
+ 'x-csrf-token': CONFIG.API.CT0,
70
+ 'cookie': `auth_token=${CONFIG.API.AUTH_TOKEN};ct0=${CONFIG.API.CT0}`
71
+ };
72
+ }
73
+
74
+ static getImageMimeType(base64String) {
75
+ const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
76
+ return matches ? matches[1] : 'image/jpeg';
77
+ }
78
+
79
+ static async handleApiResponse(response, errorMessage) {
80
+ if (!response.ok) {
81
+ throw new Error(`${errorMessage} Status: ${response.status}`);
82
+ }
83
+ return await response.json();
84
+ }
85
+ }
86
+
87
+ // 会话管理类
88
+ class ConversationManager {
89
+ static async generateNewId() {
90
+ const response = await fetch(CONFIG.API.ENDPOINTS.CREATE_CONVERSATION, {
91
+ method: 'POST',
92
+ headers: Utils.createAuthHeaders(),
93
+ body: JSON.stringify({
94
+ variables: {},
95
+ queryId: "vvC5uy7pWWHXS2aDi1FZeA"
96
+ })
97
+ });
98
+
99
+ const data = await Utils.handleApiResponse(response, '创建会话失败!');
100
+ return data.data.create_grok_conversation.conversation_id;
101
+ }
102
+
103
+ static async deleteConversation(conversationId) {
104
+ if (!conversationId) return;
105
+
106
+ await fetch(CONFIG.API.ENDPOINTS.DELETE_CONVERSATION, {
107
+ method: 'POST',
108
+ headers: Utils.createAuthHeaders(),
109
+ body: JSON.stringify({
110
+ variables: { conversationId },
111
+ queryId: "TlKHSWVMVeaa-i7dqQqFQA"
112
+ })
113
+ });
114
+ }
115
+ }
116
+
117
+ // 消息处理类
118
+ class MessageProcessor {
119
+ static createChatResponse(message, model, isStream = false) {
120
+ const baseResponse = {
121
+ id: `chatcmpl-${uuidv4()}`,
122
+ created: Math.floor(Date.now() / 1000),
123
+ model: model
124
+ };
125
+
126
+ if (isStream) {
127
+ return {
128
+ ...baseResponse,
129
+ object: 'chat.completion.chunk',
130
+ choices: [{
131
+ index: 0,
132
+ delta: { content: message }
133
+ }]
134
+ };
135
+ }
136
+
137
+ return {
138
+ ...baseResponse,
139
+ object: 'chat.completion',
140
+ choices: [{
141
+ index: 0,
142
+ message: {
143
+ role: 'assistant',
144
+ content: message
145
+ },
146
+ finish_reason: 'stop'
147
+ }],
148
+ usage: null
149
+ };
150
+ }
151
+
152
+ static processMessageContent(content) {
153
+ if (typeof content === 'string') return content;
154
+ if (Array.isArray(content)) {
155
+ if (content.some(item => item.type === 'image_url')) return null;
156
+ return content
157
+ .filter(item => item.type === 'text')
158
+ .map(item => item.text)
159
+ .join('\n');
160
+ }
161
+ if (typeof content === 'object') return content.text || null;
162
+ return null;
163
+ }
164
+ }
165
+
166
+ // Grok API 客户端类
167
+ class TwitterGrokApiClient {
168
+ constructor(modelId) {
169
+ if (!CONFIG.MODELS[modelId]) {
170
+ throw new Error(`不支持的模型: ${modelId}`);
171
+ }
172
+ this.modelId = CONFIG.MODELS[modelId];
173
+ this.modelType = {
174
+ isDeepSearch: modelId === 'grok-3-deepsearch',
175
+ isReasoning: modelId === 'grok-3-reasoning'
176
+ };
177
+ }
178
+
179
+ async uploadImage(imageData) {
180
+ const formData = new FormData();
181
+ const imageBuffer = Buffer.from(imageData.split(',')[1], 'base64');
182
+ const mimeType = Utils.getImageMimeType(imageData);
183
+
184
+ formData.append('photo', imageBuffer, {
185
+ filename: 'image.png',
186
+ contentType: mimeType
187
+ });
188
+
189
+ const response = await fetch(CONFIG.API.ENDPOINTS.UPLOAD_IMAGE, {
190
+ method: 'POST',
191
+ headers: {
192
+ ...Utils.createAuthHeaders(),
193
+ ...formData.getHeaders()
194
+ },
195
+ body: formData
196
+ });
197
+
198
+ return await Utils.handleApiResponse(response, '图片上传失败');
199
+ }
200
+
201
+ async transformMessages(messages) {
202
+ const processedMessages = [];
203
+ for (let i = 0; i < messages.length; i++) {
204
+ const isLastTwoMessages = i >= messages.length - 2;
205
+ const content = await this.processMessageContent(messages[i], isLastTwoMessages);
206
+ if (content) {
207
+ processedMessages.push(content);
208
+ }
209
+ }
210
+ return processedMessages;
211
+ }
212
+
213
+ async processMessageContent(msg, isLastTwoMessages) {
214
+ const { role, content } = msg;
215
+ let message = '';
216
+ let fileAttachments = [];
217
+
218
+ if (typeof content === 'string') {
219
+ message = content;
220
+ } else if (Array.isArray(content) || typeof content === 'object') {
221
+ const { text, imageAttachments } = await this.processComplexContent(content, isLastTwoMessages);
222
+ message = text;
223
+ fileAttachments = imageAttachments;
224
+ }
225
+
226
+ return {
227
+ message,
228
+ sender: role === 'user' ? 1 : 2,
229
+ ...(role === 'user' && { fileAttachments })
230
+ };
231
+ }
232
+
233
+ async processComplexContent(content, isLastTwoMessages) {
234
+ let text = '';
235
+ let imageAttachments = [];
236
+
237
+ const processItem = async (item) => {
238
+ if (item.type === 'text') {
239
+ text += item.text;
240
+ } else if (item.type === 'image_url' && item.image_url.url.includes('data:image')) {
241
+ if (isLastTwoMessages) {
242
+ const uploadResult = await this.uploadImage(item.image_url.url);
243
+ if (Array.isArray(uploadResult)) {
244
+ imageAttachments.push(...uploadResult);
245
+ }
246
+ } else {
247
+ text += '[图片]';
248
+ }
249
+ }
250
+ };
251
+
252
+ if (Array.isArray(content)) {
253
+ await Promise.all(content.map(processItem));
254
+ } else {
255
+ await processItem(content);
256
+ }
257
+
258
+ return { text, imageAttachments };
259
+ }
260
+
261
+ async prepareChatRequest(request) {
262
+ const responses = await this.transformMessages(request.messages);
263
+ const conversationId = await ConversationManager.generateNewId();
264
+
265
+ return {
266
+ responses,
267
+ systemPromptName: "",
268
+ grokModelOptionId: this.modelId,
269
+ conversationId,
270
+ returnSearchResults: this.modelType.isReasoning,
271
+ returnCitations: this.modelType.isReasoning,
272
+ promptMetadata: {
273
+ promptSource: "NATURAL",
274
+ action: "INPUT"
275
+ },
276
+ imageGenerationCount: 1,
277
+ requestFeatures: {
278
+ eagerTweets: false,
279
+ serverHistory: false
280
+ },
281
+ enableCustomization: true,
282
+ enableSideBySide: false,
283
+ toolOverrides: {},
284
+ isDeepsearch: this.modelType.isDeepSearch,
285
+ isReasoning: this.modelType.isReasoning
286
+ };
287
+ }
288
+ }
289
+
290
+ // 响应处理类
291
+ class ResponseHandler {
292
+ static async handleStreamResponse(response, model, res) {
293
+ res.setHeader('Content-Type', 'text/event-stream');
294
+ res.setHeader('Cache-Control', 'no-cache');
295
+ res.setHeader('Connection', 'keep-alive');
296
+
297
+ const reader = response.body;
298
+ let buffer = '';
299
+ let isImgGen = false;
300
+ let isThinking = false;
301
+
302
+ try {
303
+ for await (const chunk of reader) {
304
+ const lines = (buffer + chunk.toString()).split('\n');
305
+ buffer = lines.pop() || '';
306
+
307
+ for (const line of lines) {
308
+ if (!line.trim()) continue;
309
+ await this.processStreamLine(JSON.parse(line), model, res, isImgGen, isThinking);
310
+ }
311
+ }
312
+
313
+ res.write('data: [DONE]\n\n');
314
+ res.end();
315
+ } catch (error) {
316
+ Logger.error('Stream response error:', error, 'ChatAPI');
317
+ throw error;
318
+ }
319
+ }
320
+
321
+ static async processStreamLine(jsonData, model, res, isImgGen, isThinking) {
322
+ if (jsonData.result?.doImgGen) {
323
+ isImgGen = true;
324
+ return;
325
+ }
326
+
327
+ if (isImgGen && jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
328
+ await this.handleImageGeneration(jsonData, model, res);
329
+ return;
330
+ }
331
+
332
+ if (!isImgGen && jsonData.result?.message) {
333
+ await this.handleTextMessage(jsonData, model, res, isThinking);
334
+ }
335
+ }
336
+
337
+ static async handleImageGeneration(jsonData, model, res) {
338
+ const imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
339
+ const imageResponse = await fetch(imageUrl, {
340
+ method: 'GET',
341
+ headers: Utils.createAuthHeaders()
342
+ });
343
+
344
+ if (!imageResponse.ok) {
345
+ throw new Error(`Image request failed: ${imageResponse.status}`);
346
+ }
347
+
348
+ const imageBuffer = await imageResponse.arrayBuffer();
349
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
350
+ const imageContentType = imageResponse.headers.get('content-type');
351
+ const message = `![image](data:${imageContentType};base64,${base64Image})`;
352
+
353
+ const responseData = MessageProcessor.createChatResponse(message, model, true);
354
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
355
+ }
356
+
357
+ static async handleTextMessage(jsonData, model, res, isThinking) {
358
+ let message = jsonData.result.message;
359
+
360
+ switch (model) {
361
+ case "grok-3-reasoning":
362
+ if (!isThinking && jsonData.result?.isThinking) {
363
+ message = "<think>" + message;
364
+ isThinking = true;
365
+ } else if (isThinking && !jsonData.result?.isThinking) {
366
+ message = "</think>" + message;
367
+ isThinking = false;
368
+ }
369
+ break;
370
+ case "grok-3-deepsearch":
371
+ if (jsonData.result?.messageTag !== "final") return;
372
+ break;
373
+ }
374
+
375
+ const responseData = MessageProcessor.createChatResponse(message, model, true);
376
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
377
+ }
378
+
379
+ static async handleNormalResponse(response, model, res) {
380
+ const reader = response.body;
381
+ let buffer = '';
382
+ let fullResponse = '';
383
+ let imageUrl = null;
384
+ let isThinking = false;
385
+
386
+ try {
387
+ for await (const chunk of reader) {
388
+ const lines = (buffer + chunk.toString()).split('\n');
389
+ buffer = lines.pop() || '';
390
+
391
+ for (const line of lines) {
392
+ if (!line.trim()) continue;
393
+ const result = await this.processNormalLine(JSON.parse(line), model, isThinking);
394
+ fullResponse += result.text || '';
395
+ imageUrl = result.imageUrl || imageUrl;
396
+ isThinking = result.isThinking;
397
+ }
398
+ }
399
+
400
+ if (imageUrl) {
401
+ await this.sendImageResponse(imageUrl, model, res);
402
+ } else {
403
+ const responseData = MessageProcessor.createChatResponse(fullResponse, model);
404
+ res.json(responseData);
405
+ }
406
+ } catch (error) {
407
+ Logger.error('Normal response error:', error, 'ChatAPI');
408
+ throw error;
409
+ }
410
+ }
411
+
412
+ static async processNormalLine(jsonData, model, isThinking) {
413
+ let result = { text: '', imageUrl: null, isThinking };
414
+
415
+ if (jsonData.result?.message) {
416
+ switch (model) {
417
+ case "grok-3-reasoning":
418
+ result = this.processReasoningMessage(jsonData, isThinking);
419
+ break;
420
+ case "grok-3-deepsearch":
421
+ if (jsonData.result?.messageTag === "final") {
422
+ result.text = jsonData.result.message;
423
+ }
424
+ break;
425
+ default:
426
+ result.text = jsonData.result.message;
427
+ }
428
+ }
429
+
430
+ if (jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
431
+ result.imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
432
+ }
433
+
434
+ return result;
435
+ }
436
+
437
+ static processReasoningMessage(jsonData, isThinking) {
438
+ let result = { text: '', isThinking };
439
+
440
+ if (jsonData.result?.isThinking && !isThinking) {
441
+ result.text = "<think>" + jsonData.result.message;
442
+ result.isThinking = true;
443
+ } else if (isThinking && !jsonData.result?.isThinking) {
444
+ result.text = "</think>" + jsonData.result.message;
445
+ result.isThinking = false;
446
+ } else {
447
+ result.text = jsonData.result.message;
448
+ }
449
+
450
+ return result;
451
+ }
452
+
453
+ static async sendImageResponse(imageUrl, model, res) {
454
+ const response = await fetch(imageUrl, {
455
+ method: 'GET',
456
+ headers: Utils.createAuthHeaders()
457
+ });
458
+
459
+ if (!response.ok) {
460
+ throw new Error(`Image request failed: ${response.status}`);
461
+ }
462
+
463
+ const imageBuffer = await response.arrayBuffer();
464
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
465
+ const imageContentType = response.headers.get('content-type');
466
+
467
+ const responseData = MessageProcessor.createChatResponse(
468
+ `![image](data:${imageContentType};base64,${base64Image})`,
469
+ model
470
+ );
471
+ res.json(responseData);
472
+ }
473
+ }
474
+
475
+ // Express 应用配置
476
+ const app = express();
477
+ app.use(Logger.requestLogger);
478
+ app.use(cors(CONFIG.SERVER.CORS_OPTIONS));
479
+ app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
480
+ app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
481
+
482
+ // API 路由
483
+ app.get('/hf/v1/models', (req, res) => {
484
+ res.json({
485
+ object: "list",
486
+ data: Object.keys(CONFIG.MODELS).map(model => ({
487
+ id: model,
488
+ object: "model",
489
+ created: Math.floor(Date.now() / 1000),
490
+ owned_by: "xgrok",
491
+ }))
492
+ });
493
+ });
494
+
495
+ app.post('/hf/v1/chat/completions', async (req, res) => {
496
+ try {
497
+ const authToken = req.headers.authorization?.replace('Bearer ', '');
498
+ if (authToken !== CONFIG.API.API_KEY) {
499
+ return res.status(401).json({ error: 'Unauthorized' });
500
+ }
501
+
502
+ const grokClient = new TwitterGrokApiClient(req.body.model);
503
+ const requestPayload = await grokClient.prepareChatRequest(req.body);
504
+
505
+ const response = await fetch(CONFIG.API.ENDPOINTS.CHAT, {
506
+ method: 'POST',
507
+ headers: Utils.createAuthHeaders(),
508
+ body: JSON.stringify(requestPayload)
509
+ });
510
+
511
+ if (!response.ok) {
512
+ throw new Error(`上游服务请求失败! status: ${response.status}`);
513
+ }
514
+
515
+ await (req.body.stream
516
+ ? ResponseHandler.handleStreamResponse(response, req.body.model, res)
517
+ : ResponseHandler.handleNormalResponse(response, req.body.model, res));
518
+
519
+ } catch (error) {
520
+ Logger.error('Chat Completions Request Error', error, 'ChatAPI');
521
+ res.status(500).json({
522
+ error: {
523
+ message: error.message,
524
+ type: 'server_error',
525
+ param: null,
526
+ code: error.code || null
527
+ }
528
+ });
529
+ } finally {
530
+ if (req.body.conversationId) {
531
+ await ConversationManager.deleteConversation(req.body.conversationId);
532
+ }
533
+ }
534
+ });
535
+
536
+ // 404 处理
537
+ app.use((req, res) => {
538
+ res.status(404).json({ error: '请求路径不存在' });
539
+ });
540
+
541
+ // 启动服务器
542
+ app.listen(CONFIG.SERVER.PORT, () => {
543
+ Logger.info(`服务器运行在端口 ${CONFIG.SERVER.PORT}`, 'Server');
544
  });