alcex commited on
Commit
07986da
·
verified ·
1 Parent(s): b1ffcce

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +174 -688
index.js CHANGED
@@ -1,708 +1,194 @@
1
- import express from 'express';
2
- import fetch from 'node-fetch';
3
- import FormData from 'form-data';
4
- import dotenv from 'dotenv';
5
- import cors from 'cors';
6
- import puppeteer from 'puppeteer';
7
- import { v4 as uuidv4 } from 'uuid';
8
-
9
- dotenv.config();
10
- // 配置常量
11
- const CONFIG = {
12
- MODELS: {
13
- 'grok-latest': 'grok-latest',
14
- 'grok-latest-image': 'grok-latest',
15
- 'grok-latest-search': 'grok-latest'
16
- },
17
- API: {
18
- BASE_URL: "https://grok.com",
19
- API_KEY: process.env.API_KEY || "sk-123456",
20
- SSO_TOKEN: null,//登录时才有的认证cookie,这里暂时用不到,之后可能需要
21
- SIGNATURE_COOKIE: null,
22
- PICGO_KEY: process.env.PICGO_KEY || null //想要生图的话需要填入这个PICGO图床的key
23
- },
24
- SERVER: {
25
- PORT: process.env.PORT || 3000,
26
- BODY_LIMIT: '5mb'
27
- },
28
- RETRY: {
29
- MAX_ATTEMPTS: 1,//重试次数
30
- DELAY_BASE: 1000 // 基础延迟时间(毫秒)
31
- },
32
- ISSHOW_SEARCH_RESULTS: process.env.ISSHOW_SEARCH_RESULTS === 'true',//是否显示搜索结果,默认关闭
33
- CHROME_PATH: process.env.CHROME_PATH || "/usr/bin/chromium"//chrome路径
34
- };
35
-
36
- // 请求头配置
37
- const DEFAULT_HEADERS = {
38
- 'accept': '*/*',
39
- 'accept-language': 'zh-CN,zh;q=0.9',
40
- 'accept-encoding': 'gzip, deflate, br, zstd',
41
- 'content-type': 'text/plain;charset=UTF-8',
42
- 'Connection': 'keep-alive',
43
- 'origin': 'https://grok.com',
44
- 'priority': 'u=1, i',
45
- 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
46
- 'sec-ch-ua-mobile': '?0',
47
- 'sec-ch-ua-platform': '"Windows"',
48
- 'sec-fetch-dest': 'empty',
49
- 'sec-fetch-mode': 'cors',
50
- 'sec-fetch-site': 'same-origin',
51
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
52
- 'baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'
53
  };
54
 
55
- class Utils {
56
- static async extractGrokHeaders() {
57
- console.log("开始提取头信息");
58
- try {
59
- // 启动浏览器
60
- const browser = await puppeteer.launch({
61
- headless: true,
62
- args: [
63
- '--no-sandbox',
64
- '--disable-setuid-sandbox',
65
- '--disable-dev-shm-usage',
66
- '--disable-gpu'
67
- ],
68
- executablePath: CONFIG.CHROME_PATH
69
- });
70
-
71
- const page = await browser.newPage();
72
- await page.goto('https://grok.com/', { waitUntil: 'networkidle0' });
73
 
74
- // 获取所有 Cookies
75
- const cookies = await page.cookies();
76
- const targetHeaders = ['x-anonuserid', 'x-challenge', 'x-signature'];
77
- const extractedHeaders = {};
78
- // 遍历 Cookies
79
- for (const cookie of cookies) {
80
- // 检查是否为目标头信息
81
- if (targetHeaders.includes(cookie.name.toLowerCase())) {
82
- extractedHeaders[cookie.name.toLowerCase()] = cookie.value;
83
- }
84
- }
85
- // 关闭浏览器
86
- await browser.close();
87
- // 打印并返回提取的头信息
88
- console.log('提取的头信息:', JSON.stringify(extractedHeaders, null, 2));
89
- return extractedHeaders;
90
 
91
- } catch (error) {
92
- console.error('获取头信息出错:', error);
93
- return null;
94
- }
95
- }
96
- static async get_signature() {
97
- if (CONFIG.API.SIGNATURE_COOKIE) {
98
- return CONFIG.API.SIGNATURE_COOKIE;
99
- }
100
- console.log("刷新认证信息");
101
- let retryCount = 0;
102
- while (retryCount < CONFIG.RETRY.MAX_ATTEMPTS) {
103
- let headers = await Utils.extractGrokHeaders();
104
- if (headers) {
105
- console.log("获取认证信息成功");
106
- CONFIG.API.SIGNATURE_COOKIE = { cookie: `x-anonuserid=${headers["x-anonuserid"]}; x-challenge=${headers["x-challenge"]}; x-signature=${headers["x-signature"]}` };
107
- return CONFIG.API.SIGNATURE_COOKIE;
108
- }
109
- retryCount++;
110
- if (retryCount >= CONFIG.RETRY.MAX_ATTEMPTS) {
111
- throw new Error(`获取认证信息失败!`);
112
- }
113
- await new Promise(resolve => setTimeout(resolve, CONFIG.RETRY.DELAY_BASE * retryCount));
114
- }
115
- }
116
- static async handleError(error, res) {
117
- // 如果是500错误且提供了原始请求函数,尝试重新获取签��并重试
118
- if (String(error).includes("status: 500")) {
119
- try {
120
- await Utils.get_signature();
121
- if (CONFIG.API.SIGNATURE_COOKIE) {
122
- return res.status(500).json({
123
- error: {
124
- message: `${retryError.message}已重新刷新认证信息,请重新对话`,
125
- type: 'server_error',
126
- param: null,
127
- code: error.code || null
128
- }
129
- });
130
- } else {
131
- return res.status(500).json({
132
- error: {
133
- message: "认证信息获取失败",
134
- type: 'server_error',
135
- param: null,
136
- code: null
137
- }
138
- });
139
- }
140
- } catch (retryError) {
141
- console.error('重试失败:', retryError);
142
- return res.status(500).json({
143
- error: {
144
- message: `${retryError.message},认证信息获取失败`,
145
- type: 'server_error',
146
- param: null,
147
- code: retryError.code || null
148
- }
149
- });
150
- }
151
- }
152
-
153
- // 其他错误直接返回
154
- res.status(500).json({
155
- error: {
156
- message: error.message,
157
- type: 'server_error',
158
- param: null,
159
- code: error.code || null
160
- }
161
- });
162
- }
163
- static async organizeSearchResults(searchResults) {
164
- // 确保传入的是有效的搜索结果对象
165
- if (!searchResults || !searchResults.results) {
166
- return '';
167
- }
168
-
169
- const results = searchResults.results;
170
- const formattedResults = results.map((result, index) => {
171
- // 处理可能为空的字段
172
- const title = result.title || '未知标题';
173
- const url = result.url || '#';
174
- const preview = result.preview || '无预览内容';
175
-
176
- return `\r\n<details><summary>资料[${index}]: ${title}</summary>\r\n${preview}\r\n\n[Link](${url})\r\n</details>`;
177
- });
178
- return formattedResults.join('\n\n');
179
- }
180
- }
181
-
182
-
183
- class GrokApiClient {
184
- constructor(modelId) {
185
- if (!CONFIG.MODELS[modelId]) {
186
- throw new Error(`不支持的模型: ${modelId}`);
187
- }
188
- this.modelId = CONFIG.MODELS[modelId];
189
- }
190
-
191
- processMessageContent(content) {
192
- if (typeof content === 'string') return content;
193
- return null;
194
- }
195
- // 获取图片类型
196
- getImageType(base64String) {
197
- let mimeType = 'image/jpeg';
198
- if (base64String.includes('data:image')) {
199
- const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
200
- if (matches) {
201
- mimeType = matches[1];
202
- }
203
- }
204
- const extension = mimeType.split('/')[1];
205
- const fileName = `image.${extension}`;
206
-
207
- return {
208
- mimeType: mimeType,
209
- fileName: fileName
210
- };
211
- }
212
-
213
- async uploadBase64Image(base64Data, url) {
214
- try {
215
- // 处理 base64 数据
216
- let imageBuffer;
217
- if (base64Data.includes('data:image')) {
218
- imageBuffer = base64Data.split(',')[1];
219
- } else {
220
- imageBuffer = base64Data
221
- }
222
- const { mimeType, fileName } = this.getImageType(base64Data);
223
- let uploadData = {
224
- rpc: "uploadFile",
225
- req: {
226
- fileName: fileName,
227
- fileMimeType: mimeType,
228
- content: imageBuffer
229
- }
230
- };
231
- console.log("发送图片请求");
232
- // 发送请求
233
- const response = await fetch(url, {
234
- method: 'POST',
235
- headers: {
236
- ...CONFIG.DEFAULT_HEADERS,
237
- ...CONFIG.API.SIGNATURE_COOKIE
238
- },
239
- body: JSON.stringify(uploadData)
240
- });
241
-
242
- if (!response.ok) {
243
- console.error(`上传图片失败,状态码:${response.status},原因:${response.error}`);
244
- return '';
245
- }
246
-
247
- const result = await response.json();
248
- console.log('上传图片成功:', result);
249
- return result.fileMetadataId;
250
-
251
- } catch (error) {
252
- console.error('上传图片失败:', error);
253
- return '';
254
- }
255
- }
256
-
257
- async prepareChatRequest(request) {
258
-
259
- if (request.model === 'grok-latest-image' && !CONFIG.API.PICGO_KEY) {
260
- throw new Error(`该模型需要配置PICGO图床密钥!`);
261
- }
262
- var todoMessages = request.messages;
263
- if (request.model === 'grok-latest-image' || request.model === 'grok-latest-search') {
264
- todoMessages = Array.isArray(todoMessages) ? [todoMessages[todoMessages.length - 1]] : [todoMessages];;
265
- }
266
- let fileAttachments = [];
267
- let messages = '';
268
- let lastRole = null;
269
- let lastContent = '';
270
- let search = false;
271
-
272
- const processImageUrl = async (content) => {
273
- if (content.type === 'image_url' && content.image_url.url.includes('data:image')) {
274
- const imageResponse = await this.uploadBase64Image(
275
- content.image_url.url,
276
- `${CONFIG.API.BASE_URL}/api/rpc`
277
- );
278
- return imageResponse;
279
- }
280
- return null;
281
- };
282
-
283
- for (const current of todoMessages) {
284
- const role = current.role === 'assistant' ? 'assistant' : 'user';
285
- let textContent = '';
286
- // 处理消息内容
287
- if (Array.isArray(current.content)) {
288
- // 处理数组内的所有内容
289
- for (const item of current.content) {
290
- if (item.type === 'image_url') {
291
- // 如果是图片且是最后一条消息,则处理图片
292
- if (current === todoMessages[todoMessages.length - 1]) {
293
- const processedImage = await processImageUrl(item);
294
- if (processedImage) fileAttachments.push(processedImage);
295
- }
296
- textContent += (textContent ? '\n' : '') + "[图片]";//图片占位符
297
- } else if (item.type === 'text') {
298
- textContent += (textContent ? '\n' : '') + item.text;
299
- }
300
- }
301
- } else if (typeof current.content === 'object' && current.content !== null) {
302
- // 处理单个对象内容
303
- if (current.content.type === 'image_url') {
304
- // 如果是图片且是最后一条消息,则处理图片
305
- if (current === todoMessages[todoMessages.length - 1]) {
306
- const processedImage = await processImageUrl(current.content);
307
- if (processedImage) fileAttachments.push(processedImage);
308
- }
309
- textContent += (textContent ? '\n' : '') + "[图片]";//图片占位符
310
- } else if (current.content.type === 'text') {
311
- textContent = current.content.text;
312
- }
313
- } else {
314
- // 处理普通文本内容
315
- textContent = this.processMessageContent(current.content);
316
- }
317
- // 添加文本内容到消息字符串
318
- if (textContent) {
319
- if (role === lastRole) {
320
- // 如果角色相同,合并消息内容
321
- lastContent += '\n' + textContent;
322
- messages = messages.substring(0, messages.lastIndexOf(`${role.toUpperCase()}: `)) +
323
- `${role.toUpperCase()}: ${lastContent}\n`;
324
- } else {
325
- // 如果角色不同,添加新的消息
326
- messages += `${role.toUpperCase()}: ${textContent}\n`;
327
- lastContent = textContent;
328
- lastRole = role;
329
- }
330
- } else if (current === todoMessages[todoMessages.length - 1] && fileAttachments.length > 0) {
331
- // 如果是最后一条消息且有图片附件,添加空消息占位
332
- messages += `${role.toUpperCase()}: [图片]\n`;
333
- }
334
- }
335
-
336
- if (fileAttachments.length > 4) {
337
- fileAttachments = fileAttachments.slice(0, 4); // 最多上传4张
338
- }
339
-
340
- messages = messages.trim();
341
-
342
- if (request.model === 'grok-latest-search') {
343
- search = true;
344
- }
345
- return {
346
- message: messages,
347
- modelName: this.modelId,
348
- disableSearch: false,
349
- imageAttachments: [],
350
- returnImageBytes: false,
351
- returnRawGrokInXaiRequest: false,
352
- fileAttachments: fileAttachments,
353
- enableImageStreaming: false,
354
- imageGenerationCount: 1,
355
- toolOverrides: {
356
- imageGen: request.model === 'grok-latest-image',
357
- webSearch: search,
358
- xSearch: search,
359
- xMediaSearch: search,
360
- trendsSearch: search,
361
- xPostAnalyze: search
362
- }
363
- };
364
- }
365
- }
366
-
367
- class MessageProcessor {
368
- static createChatResponse(message, model, isStream = false) {
369
- const baseResponse = {
370
- id: `chatcmpl-${uuidv4()}`,
371
- created: Math.floor(Date.now() / 1000),
372
- model: model
373
- };
374
-
375
- if (isStream) {
376
- return {
377
- ...baseResponse,
378
- object: 'chat.completion.chunk',
379
- choices: [{
380
- index: 0,
381
- delta: {
382
- content: message
383
- }
384
- }]
385
- };
386
- }
387
 
388
- return {
389
- ...baseResponse,
390
- object: 'chat.completion',
391
- choices: [{
392
- index: 0,
393
- message: {
394
- role: 'assistant',
395
- content: message
396
- },
397
- finish_reason: 'stop'
398
- }],
399
- usage: null
400
- };
401
- }
402
- }
403
 
404
- // 中间件配置
405
- const app = express();
406
- app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
407
- app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
408
- app.use(cors({
409
- origin: '*',
410
- methods: ['GET', 'POST', 'OPTIONS'],
411
- allowedHeaders: ['Content-Type', 'Authorization']
412
- }));
413
- // API路由
414
- app.get('/hf/v1/models', (req, res) => {
415
- res.json({
416
- object: "list",
417
- data: Object.keys(CONFIG.MODELS).map((model, index) => ({
418
- id: model,
419
- object: "model",
420
- created: Math.floor(Date.now() / 1000),
421
- owned_by: "xai",
422
- }))
 
 
 
 
 
 
423
  });
424
- });
425
-
426
- app.post('/hf/v1/chat/completions', async (req, res) => {
427
- const authToken = req.headers.authorization?.replace('Bearer ', '');
428
- if (authToken !== CONFIG.API.API_KEY) {
429
- return res.status(401).json({ error: 'Unauthorized' });
430
- }
431
- const makeRequest = async () => {
432
- if (!CONFIG.API.SIGNATURE_COOKIE) {
433
- await Utils.get_signature();
434
- }
435
- const grokClient = new GrokApiClient(req.body.model);
436
- const requestPayload = await grokClient.prepareChatRequest(req.body);
437
- //创建新对话
438
- const newMessageReq = await fetch(`${CONFIG.API.BASE_URL}/api/rpc`, {
439
- method: 'POST',
440
- headers: {
441
- ...DEFAULT_HEADERS,
442
- ...CONFIG.API.SIGNATURE_COOKIE
443
- },
444
- body: JSON.stringify({
445
- rpc: "createConversation",
446
- req: {
447
- temporary: false
448
- }
449
- })
450
- });
451
- if (!newMessageReq.ok) {
452
- throw new Error(`上游服务请求失败! status: ${newMessageReq.status}`);
453
- }
454
-
455
- // 获取响应文本
456
- const responseText = await newMessageReq.json();
457
- const conversationId = responseText.conversationId;
458
- console.log("会话ID:conversationId", conversationId);
459
- if (!conversationId) {
460
- throw new Error(`创建会话失败! status: ${newMessageReq.status}`);
461
- }
462
- //发送对话
463
- const response = await fetch(`${CONFIG.API.BASE_URL}/api/conversations/${conversationId}/responses`, {
464
- method: 'POST',
465
- headers: {
466
- "accept": "text/event-stream",
467
- "baggage": "sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c",
468
- "content-type": "text/plain;charset=UTF-8",
469
- "Connection": "keep-alive",
470
- ...CONFIG.API.SIGNATURE_COOKIE
471
- },
472
- body: JSON.stringify(requestPayload)
473
- });
474
-
475
- if (!response.ok) {
476
- throw new Error(`上游服务请求失败! status: ${response.status}`);
477
- }
478
- if (req.body.stream) {
479
- await handleStreamResponse(response, req.body.model, res);
480
- } else {
481
- await handleNormalResponse(response, req.body.model, res);
482
- }
483
- }
484
- try {
485
- await makeRequest();
486
- } catch (error) {
487
- CONFIG.API.SIGNATURE_COOKIE = null;
488
- await Utils.handleError(error, res);
489
- }
490
- });
491
-
492
- async function handleStreamResponse(response, model, res) {
493
- res.setHeader('Content-Type', 'text/event-stream');
494
- res.setHeader('Cache-Control', 'no-cache');
495
- res.setHeader('Connection', 'keep-alive');
496
-
497
- try {
498
- const stream = response.body;
499
- let buffer = '';
500
 
501
- stream.on('data', async (chunk) => {
502
- buffer += chunk.toString();
503
- const lines = buffer.split('\n');
504
- buffer = lines.pop() || '';
505
-
506
- for (const line of lines) {
507
- if (!line.trim()) continue;
508
- const trimmedLine = line.trim();
509
- if (trimmedLine.startsWith('data: ')) {
510
- const data = trimmedLine.substring(6);
511
- if(data === "[DONE]"){
512
- console.log("流结束");
513
- res.write('data: [DONE]\n\n');
514
- return res.end();
515
- };
516
- try {
517
- if(!data.trim())continue;
518
- const linejosn = JSON.parse(data);
519
- if (linejosn?.error?.name === "RateLimitError") {
520
- var responseData = MessageProcessor.createChatResponse(`${linejosn.error.name},请重新对话`, model, true);
521
- CONFIG.API.SIGNATURE_COOKIE = null;
522
- console.log("认证信息已删除");
523
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
524
- res.write('data: [DONE]\n\n');
525
- return res.end();
526
- }
527
- switch (model) {
528
- case 'grok-latest-image':
529
- if (linejosn.response === "modelResponse" && linejosn?.modelResponse?.generatedImageUrls) {
530
- const dataImage = await handleImageResponse(linejosn.modelResponse.generatedImageUrls);
531
- const responseData = MessageProcessor.createChatResponse(dataImage, model, true);
532
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
533
- }
534
- break;
535
- case 'grok-latest':
536
- if (linejosn.response === "token") {
537
- const token = linejosn.token;
538
- if (token && token.length > 0) {
539
- const responseData = MessageProcessor.createChatResponse(token, model, true);
540
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
541
- }
542
- }
543
- break;
544
- case 'grok-latest-search':
545
- if (linejosn.response === "token") {
546
- const token = linejosn.token;
547
- if (token && token.length > 0) {
548
- const responseData = MessageProcessor.createChatResponse(token, model, true);
549
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
550
- }
551
- }
552
- if (linejosn.response === "webSearchResults" && CONFIG.ISSHOW_SEARCH_RESULTS) {
553
- const searchResults = await Utils.organizeSearchResults(linejosn.webSearchResults);
554
- const responseData = MessageProcessor.createChatResponse(`<think>\r\n${searchResults}\r\n</think>\r\n`, model, true);
555
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
556
- }
557
- break;
558
- }
559
- } catch (error) {
560
- console.error('JSON解析错误:', error);
561
- }
562
- }
563
- }
564
- });
565
- stream.on('error', (error) => {
566
- console.error('流处理错误:', error);
567
- res.write('data: [DONE]\n\n');
568
- res.end();
569
- });
570
-
571
- } catch (error) {
572
- console.error('处理响应错误:', error);
573
- res.write('data: [DONE]\n\n');
574
- res.end();
575
- }
576
- }
577
-
578
- async function handleNormalResponse(response, model, res) {
579
  let fullResponse = '';
580
- let imageUrl = '';
581
-
582
- try {
583
- const responseText = await response.text();
584
- const lines = responseText.split('\n');
585
 
586
- for (const line of lines) {
587
- if (!line.trim()) continue;
588
- const data = line.slice(6);
589
- if (data === '[DONE]') continue;
590
- let linejosn = JSON.parse(data);
591
- if (linejosn?.error?.name === "RateLimitError") {
592
- fullResponse = `${linejosn.error.name},请重新对话`;
593
- CONFIG.API.SIGNATURE_COOKIE = null;
594
- return res.json(MessageProcessor.createChatResponse(fullResponse, model));
595
- }
596
- switch (model) {
597
- case 'grok-latest-image':
598
- if (linejosn.response === "modelResponse" && linejosn?.modelResponse?.generatedImageUrls) {
599
- imageUrl = linejosn.modelResponse.generatedImageUrls;
600
- }
601
- break;
602
- case 'grok-latest':
603
- if (linejosn.response === "token") {
604
- const token = linejosn.token;
605
- if (token && token.length > 0) {
606
- fullResponse += token;
607
- }
608
- }
609
- break;
610
- case 'grok-latest-search':
611
- if (linejosn.response === "token") {
612
- const token = linejosn.token;
613
- if (token && token.length > 0) {
614
- fullResponse += token;
615
- }
616
- }
617
- if (linejosn.response === "webSearchResults" && CONFIG.ISSHOW_SEARCH_RESULTS) {
618
- fullResponse += `\r\n<think>${await Utils.organizeSearchResults(linejosn.webSearchResults)}</think>\r\n`;
619
- }
620
- break;
621
- }
622
- }
623
- if (imageUrl) {
624
- const dataImage = await handleImageResponse(imageUrl);
625
- const responseData = MessageProcessor.createChatResponse(dataImage, model);
626
- res.json(responseData);
627
- } else {
628
- const responseData = MessageProcessor.createChatResponse(fullResponse, model);
629
- res.json(responseData);
630
- }
631
- } catch (error) {
632
- Utils.handleError(error, res);
633
- }
634
- }
635
- async function handleImageResponse(imageUrl) {
636
- //对服务器发送图片请求
637
- const MAX_RETRIES = 3;
638
- let retryCount = 0;
639
- let imageBase64Response;
640
-
641
- while (retryCount < MAX_RETRIES) {
642
  try {
643
- //发送图片请求获取图片
644
- imageBase64Response = await fetch(`https://assets.grok.com/${imageUrl}`, {
645
- method: 'GET',
646
- headers: {
647
- ...DEFAULT_HEADERS,
648
- ...CONFIG.API.SIGNATURE_COOKIE
649
- }
650
- });
651
-
652
- if (imageBase64Response.ok) {
653
- break; // 如果请求成功,跳出重试循环
654
- }
655
-
656
- retryCount++;
657
- if (retryCount === MAX_RETRIES) {
658
- throw new Error(`上游服务请求失败! status: ${imageBase64Response.status}`);
659
- }
660
-
661
- // 等待一段时间后重试(可以使用指数退避)
662
- await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
663
-
664
- } catch (error) {
665
- retryCount++;
666
- if (retryCount === MAX_RETRIES) {
667
- throw error;
668
- }
669
- // 等待一段时间后重试
670
- await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
671
  }
 
672
  }
673
 
674
- const arrayBuffer = await imageBase64Response.arrayBuffer();
675
- const imageBuffer = Buffer.from(arrayBuffer);
676
- const formData = new FormData();
 
 
677
 
678
- formData.append('source', imageBuffer, {
679
- filename: 'new.jpg',
680
- contentType: 'image/jpeg'
681
- });
682
- const formDataHeaders = formData.getHeaders();
683
- const responseURL = await fetch("https://www.picgo.net/api/1/upload", {
684
- method: "POST",
685
- headers: {
686
- ...formDataHeaders,
687
- "Content-Type": "multipart/form-data",
688
- "X-API-Key": CONFIG.API.PICGO_KEY
689
- },
690
- body: formData
 
 
691
  });
692
- if (!responseURL.ok) {
693
- return "生图失败,请查看图床密钥是否设置正确"
694
- } else {
695
- const result = await responseURL.json();
696
- return `![image](${result.image.url})`
697
- }
698
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
699
 
700
- // 404处理
701
- app.use((req, res) => {
702
- res.status(200).send('api运行正常');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  });
704
 
705
- // 启动服务器
706
- app.listen(CONFIG.SERVER.PORT, () => {
707
- console.log(`服务器已启动,监听端口: ${CONFIG.SERVER.PORT}`);
708
  });
 
1
+ const express = require('express');
2
+ const fetch = require('node-fetch');
3
+ const { v4: uuidv4 } = require('uuid');
4
+ const app = express();
5
+ require('dotenv').config();
6
+
7
+ const modelMapping = {
8
+ "gpt-4o-mini": "GPT-4o mini",
9
+ "claude-haiku": "Claude Haiku",
10
+ "llama-3": "Llama 3",
11
+ "gemini-1.5": "Gemini 1.5",
12
+ "gemini-flash": "Gemini Flash",
13
+ "command-r": "Command R"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  };
15
 
16
+ app.use(express.json());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
+ const getCurrentTimestamp = () => Math.floor(Date.now() / 1000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ const splitIntoChunks = (text, chunkSize) => {
21
+ const chunks = [];
22
+ for (let i = 0; i < text.length; i += chunkSize) {
23
+ chunks.push(text.slice(i, i + chunkSize));
24
+ }
25
+ return chunks;
26
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ const getTempUserID = async () => {
29
+ try {
30
+ const response = await fetch('https://playground.julius.ai/api/temp_user_id');
31
+ const data = await response.json();
32
+ return data.temp_user_id;
33
+ } catch (error) {
34
+ throw new Error('Failed to get temp user ID');
35
+ }
36
+ };
 
 
 
 
 
 
37
 
38
+ const sendToJulius = async (tempUserID, message, model) => {
39
+ const conversationID = uuidv4();
40
+ const juliusReq = {
41
+ message: {
42
+ content: message,
43
+ role: "user"
44
+ },
45
+ provider: "default",
46
+ chat_mode: "auto",
47
+ client_version: "20240130",
48
+ theme: "dark",
49
+ selectedModels: [model]
50
+ };
51
+
52
+ try {
53
+ const response = await fetch('https://playground.julius.ai/api/chat/message', {
54
+ method: 'POST',
55
+ headers: {
56
+ 'is-demo': tempUserID,
57
+ 'Content-Type': 'application/json',
58
+ 'Platform': 'web',
59
+ 'conversation-id': conversationID,
60
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36'
61
+ },
62
+ body: JSON.stringify(juliusReq)
63
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  let fullResponse = '';
 
 
 
 
 
67
 
68
+ while (true) {
69
+ const { done, value } = await reader.read();
70
+ if (done) break;
71
+
72
+ const lines = value.split('\n');
73
+ for (const line of lines) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  try {
75
+ const jsonResp = JSON.parse(line);
76
+ if (jsonResp.content) {
77
+ fullResponse += jsonResp.content;
78
+ }
79
+ } catch (e) {
80
+ // 忽略解析错误
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  }
82
+ }
83
  }
84
 
85
+ return fullResponse;
86
+ } catch (error) {
87
+ throw new Error('Failed to send to Julius');
88
+ }
89
+ };
90
 
91
+ app.all('*', async (req, res) => {
92
+ // 身份验证
93
+ const authToken = process.env.AUTH_TOKEN;
94
+ if (authToken) {
95
+ const requestToken = req.headers.authorization?.replace('Bearer ', '');
96
+ if (!requestToken || requestToken !== authToken) {
97
+ return res.status(401).send('Access Denied');
98
+ }
99
+ }
100
+
101
+ // 健康检查
102
+ if (req.path !== '/v1/chat/completions') {
103
+ return res.json({
104
+ status: "Julius2Api Service Running...",
105
+ message: "MoLoveSze..."
106
  });
107
+ }
108
+
109
+ if (req.method !== 'POST') {
110
+ return res.status(405).send('Method not allowed');
111
+ }
112
+
113
+ try {
114
+ const openAIReq = req.body;
115
+ const isStream = openAIReq.stream || false;
116
+
117
+ // 模型映射
118
+ const mappedModel = modelMapping[openAIReq.model] || 'GPT-4o mini';
119
+ openAIReq.model = mappedModel;
120
+
121
+ const tempUserID = await getTempUserID();
122
+ const juliusResp = await sendToJulius(tempUserID,
123
+ openAIReq.messages[openAIReq.messages.length - 1].content,
124
+ mappedModel
125
+ );
126
+
127
+ const respId = `chatcmpl-${tempUserID}`;
128
+ const created = getCurrentTimestamp();
129
+
130
+ if (isStream) {
131
+ res.writeHead(200, {
132
+ 'Content-Type': 'text/event-stream',
133
+ 'Cache-Control': 'no-cache',
134
+ 'Connection': 'keep-alive'
135
+ });
136
+
137
+ // 发送初始响应
138
+ const firstResponse = {
139
+ id: respId,
140
+ object: "chat.completion.chunk",
141
+ created: created,
142
+ model: mappedModel,
143
+ choices: [{
144
+ delta: { role: "assistant" },
145
+ index: 0
146
+ }]
147
+ };
148
+ res.write(`data: ${JSON.stringify(firstResponse)}\n\n`);
149
+
150
+ // 分块发送内容
151
+ const chunks = splitIntoChunks(juliusResp, 50);
152
+ chunks.forEach((chunk, index) => {
153
+ const response = {
154
+ id: respId,
155
+ object: "chat.completion.chunk",
156
+ created: created,
157
+ model: mappedModel,
158
+ choices: [{
159
+ delta: { content: chunk },
160
+ index: 0,
161
+ finish_reason: index === chunks.length - 1 ? 'stop' : null
162
+ }]
163
+ };
164
+ res.write(`data: ${JSON.stringify(response)}\n\n`);
165
+ });
166
 
167
+ res.write('data: [DONE]\n\n');
168
+ res.end();
169
+ } else {
170
+ const response = {
171
+ id: respId,
172
+ object: "chat.completion",
173
+ created: created,
174
+ model: mappedModel,
175
+ choices: [{
176
+ message: {
177
+ role: "assistant",
178
+ content: juliusResp
179
+ },
180
+ finish_reason: "stop"
181
+ }]
182
+ };
183
+ res.json(response);
184
+ }
185
+ } catch (error) {
186
+ console.error(error);
187
+ res.status(500).send(error.message);
188
+ }
189
  });
190
 
191
+ const PORT = process.env.PORT || 3000;
192
+ app.listen(PORT, () => {
193
+ console.log(`Server running on port ${PORT}`);
194
  });