iseehf commited on
Commit
0e352ad
·
verified ·
1 Parent(s): fa02413

Upload 5 files

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