nomid2 commited on
Commit
86c3fd8
·
verified ·
1 Parent(s): 690c032

Upload 2 files

Browse files
Files changed (1) hide show
  1. src/router/chat.js +116 -336
src/router/chat.js CHANGED
@@ -5,50 +5,10 @@ const { v4: uuidv4 } = require('uuid')
5
  const { MODEL_MAPPING, MAMMOUTH_API_URL, AUTH_TOKEN, UNLIMITED_MODELS } = require('../config')
6
  const accountManager = require('../lib/manager')
7
  const imageUploader = require('../lib/uploader')
 
8
 
9
  const router = express.Router()
10
 
11
- // 图片格式检测和调试函数
12
- function debugImageFormats(messages, requestId, timestamp) {
13
- console.log(`[${timestamp}] 🔍 [${requestId}] === 开始详细图片格式检测 ===`)
14
-
15
- if (!messages || !Array.isArray(messages)) {
16
- console.log(`[${timestamp}] ❌ [${requestId}] 消息不是数组格式`)
17
- return
18
- }
19
-
20
- messages.forEach((msg, msgIndex) => {
21
- console.log(`[${timestamp}] 📝 [${requestId}] 消息${msgIndex + 1}:`)
22
- console.log(`[${timestamp}] 📝 [${requestId}] 角色: ${msg.role}`)
23
- console.log(`[${timestamp}] 📝 [${requestId}] 内容类型: ${Array.isArray(msg.content) ? 'array' : typeof msg.content}`)
24
-
25
- if (Array.isArray(msg.content)) {
26
- msg.content.forEach((part, partIndex) => {
27
- console.log(`[${timestamp}] 🔍 [${requestId}] 部分${partIndex + 1}:`)
28
- console.log(`[${timestamp}] 🔍 [${requestId}] 类型: ${part.type}`)
29
- console.log(`[${timestamp}] 🔍 [${requestId}] 键: ${Object.keys(part).join(', ')}`)
30
-
31
- // 检测各种可能的图片格式
32
- if (part.type === 'image_url') {
33
- console.log(`[${timestamp}] ✅ [${requestId}] → 标准OpenAI图片格式`)
34
- } else if (part.type === 'text' && part.text && part.text.includes('data:image/')) {
35
- console.log(`[${timestamp}] ✅ [${requestId}] → 文本中包含base64图片`)
36
- } else if (part.image || part.image_data || part.data) {
37
- console.log(`[${timestamp}] ✅ [${requestId}] → 直接图片数据字段`)
38
- } else if (part.type === 'image' || part.type === 'image_base64') {
39
- console.log(`[${timestamp}] ✅ [${requestId}] → 非标准图片类型`)
40
- } else {
41
- console.log(`[${timestamp}] ❓ [${requestId}] → 未识别的格式`)
42
- }
43
- })
44
- } else if (typeof msg.content === 'string' && msg.content.includes('data:image/')) {
45
- console.log(`[${timestamp}] ✅ [${requestId}] → 字符串消息中包含base64图片`)
46
- }
47
- })
48
-
49
- console.log(`[${timestamp}] 🔍 [${requestId}] === 图片格式检测完成 ===`)
50
- }
51
-
52
  // API密钥认证中间件
53
  const authenticate = (req, res, next) => {
54
  const authHeader = req.headers.authorization || req.headers.Authorization || req.headers['x-api-key']
@@ -84,7 +44,7 @@ function isUnlimitedModel(model) {
84
  }
85
 
86
  // 将OpenAI格式转换为Mammouth格式
87
- async function convertOpenAIToMammouth(openaiRequest) {
88
  const form = new FormData()
89
 
90
  // 模型选择
@@ -109,6 +69,21 @@ async function convertOpenAIToMammouth(openaiRequest) {
109
  form.append('preprompt', preprompt)
110
 
111
  // 处理非system角色的消息
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  for (const message of regularMessages) {
113
  // 处理包含图片的消息
114
  const imagesData = []
@@ -120,124 +95,48 @@ async function convertOpenAIToMammouth(openaiRequest) {
120
 
121
  // 遍历内容部分
122
  for (const part of message.content) {
123
- console.log(`[${new Date().toISOString()}] 🔍 处理消息部分:`, JSON.stringify(part, null, 2))
124
-
125
  if (part.type === 'text') {
126
  textParts.push(part.text)
127
- // 检查文本中是否包含base64图片
128
- if (part.text && part.text.includes('data:image/')) {
129
- console.log(`[${new Date().toISOString()}] 🖼️ 在文本中发现base64图片`)
130
- try {
131
- const imageId = `img_text_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`
132
- console.log(`[${new Date().toISOString()}] 🔄 [${imageId}] 开始处理文本中的base64图片`)
133
- const uploadedImageUrls = await imageUploader.uploadFromBase64(part.text)
134
- console.log(`[${new Date().toISOString()}] ✅ [${imageId}] 文本中的图片处理完成`)
135
-
136
- if (Array.isArray(uploadedImageUrls)) {
137
- imagesData.push(...uploadedImageUrls)
138
- } else {
139
- imagesData.push(uploadedImageUrls)
140
- }
141
- } catch (error) {
142
- console.error(`[${new Date().toISOString()}] ❌ 处理文本中的图片失败:`, error.message)
143
- }
144
- }
145
  } else if (part.type === 'image_url') {
146
- const imageId = `img_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`
147
  try {
148
- console.log(`[${new Date().toISOString()}] 🖼️ [${imageId}] 开始处理图片`)
149
-
150
  // 获取图片数据
151
  let imageUrl = part.image_url
152
  if (typeof imageUrl === 'object' && imageUrl.url) {
153
  imageUrl = imageUrl.url
154
  }
155
 
156
- console.log(`[${new Date().toISOString()}] 🔍 [${imageId}] 图片类型: ${imageUrl.startsWith('data:image') ? 'base64' : 'URL'}`)
157
- console.log(`[${new Date().toISOString()}] 📏 [${imageId}] 图片源长度: ${imageUrl.length} 字符`)
158
-
159
- // 处理base64图片
160
  if (imageUrl.startsWith('data:image')) {
161
- console.log(`[${new Date().toISOString()}] 🔄 [${imageId}] 开始处理base64图片`)
162
- const uploadedImageUrls = await imageUploader.uploadFromBase64(imageUrl)
163
- console.log(`[${new Date().toISOString()}] ✅ [${imageId}] base64图片处理完成,返回${Array.isArray(uploadedImageUrls) ? uploadedImageUrls.length : 1}个URL`)
164
-
165
- // 智能处理可能返回多个URL(裁切后的图片)
166
- if (Array.isArray(uploadedImageUrls)) {
167
- imagesData.push(...uploadedImageUrls)
168
- } else {
169
- imagesData.push(uploadedImageUrls)
170
- }
171
  } else {
172
- // 处理URL图片
173
- console.log(`[${new Date().toISOString()}] 🔄 [${imageId}] 开始处理URL图片`)
174
- const uploadedImageUrls = await imageUploader.uploadFromUrl(imageUrl)
175
- console.log(`[${new Date().toISOString()}] ✅ [${imageId}] URL图片处理完成,返回${Array.isArray(uploadedImageUrls) ? uploadedImageUrls.length : 1}个URL`)
176
-
177
- // 智能处理可能返回多个URL(裁切后的图片)
178
- if (Array.isArray(uploadedImageUrls)) {
179
- imagesData.push(...uploadedImageUrls)
180
- } else {
181
- imagesData.push(uploadedImageUrls)
182
- }
183
- }
184
- } catch (error) {
185
- console.error(`[${new Date().toISOString()}] ❌ [${imageId}] 图片处理错误:`, error.message)
186
- console.error(`[${new Date().toISOString()}] 🔍 [${imageId}] 错误堆栈:`, error.stack?.split('\n')[0] || 'No stack trace')
187
- }
188
- } else if (part.type === 'image' || part.type === 'image_base64') {
189
- // 处理非标准格式的图片
190
- const imageId = `img_alt_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`
191
- try {
192
- console.log(`[${new Date().toISOString()}] 🖼️ [${imageId}] 处理非标准格式图片: ${part.type}`)
193
-
194
- let imageData = part.image || part.image_data || part.data || part.url
195
- if (imageData) {
196
- if (imageData.startsWith('data:image')) {
197
- const uploadedImageUrls = await imageUploader.uploadFromBase64(imageData)
198
- if (Array.isArray(uploadedImageUrls)) {
199
- imagesData.push(...uploadedImageUrls)
200
- } else {
201
- imagesData.push(uploadedImageUrls)
202
- }
203
- } else {
204
- const uploadedImageUrls = await imageUploader.uploadFromUrl(imageData)
205
- if (Array.isArray(uploadedImageUrls)) {
206
- imagesData.push(...uploadedImageUrls)
207
- } else {
208
- imagesData.push(uploadedImageUrls)
209
- }
210
- }
211
  }
 
 
 
 
 
 
212
  } catch (error) {
213
- console.error(`[${new Date().toISOString()}] ❌ [${imageId}] 非标准格式图片处理错误:`, error.message)
214
- }
215
- } else if (part.image || part.image_data || part.data) {
216
- // 处理直接包含图片数据的情况
217
- const imageId = `img_direct_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`
218
- try {
219
- console.log(`[${new Date().toISOString()}] 🖼️ [${imageId}] 处理直接图片数据`)
220
-
221
- let imageData = part.image || part.image_data || part.data
222
- if (typeof imageData === 'string') {
223
- if (imageData.startsWith('data:image')) {
224
- const uploadedImageUrls = await imageUploader.uploadFromBase64(imageData)
225
- if (Array.isArray(uploadedImageUrls)) {
226
- imagesData.push(...uploadedImageUrls)
227
- } else {
228
- imagesData.push(uploadedImageUrls)
229
- }
230
- } else {
231
- const uploadedImageUrls = await imageUploader.uploadFromUrl(imageData)
232
- if (Array.isArray(uploadedImageUrls)) {
233
- imagesData.push(...uploadedImageUrls)
234
- } else {
235
- imagesData.push(uploadedImageUrls)
236
- }
237
- }
238
  }
239
- } catch (error) {
240
- console.error(`[${new Date().toISOString()}] ❌ [${imageId}] 直接图片数据处理错误:`, error.message)
241
  }
242
  }
243
  }
@@ -255,41 +154,20 @@ async function convertOpenAIToMammouth(openaiRequest) {
255
  imagesData: imagesData,
256
  documentsData: []
257
  }))
258
-
259
- console.log(`[${new Date().toISOString()}] 📊 消息处理完成 - 文本内容: ${content.length}字符, 图片数量: ${imagesData.length}`)
260
  }
261
 
262
- // 统计总图片数
263
- let totalImages = 0
264
- regularMessages.forEach(message => {
265
- if (Array.isArray(message.content)) {
266
- message.content.forEach(part => {
267
- if (part.type === 'image_url' || part.image || part.image_data || part.data) {
268
- totalImages++
269
- }
270
- })
271
- }
272
- })
273
-
274
- console.log(`[${new Date().toISOString()}] 🎯 请求转换完成`)
275
- console.log(`[${new Date().toISOString()}] 📊 统计信息:`)
276
- console.log(`[${new Date().toISOString()}] - 总消息数: ${regularMessages.length}`)
277
- console.log(`[${new Date().toISOString()}] - 检测到的图片数: ${totalImages}`)
278
- console.log(`[${new Date().toISOString()}] - 模型: ${form._streams ? form._streams.find(s => s[0] === 'model')?.[1] : '未知'}`)
279
- console.log(`[${new Date().toISOString()}] - FormData类型: ${form.getHeaders ? 'valid' : 'invalid'}`)
280
-
281
  return form
282
  }
283
 
284
  // 处理流数据
285
- async function handleStreamResponse(axiosResponse, res, requestedModel) {
286
- const requestId = uuidv4()
287
  const timestamp = Math.floor(Date.now() / 1000)
288
  const decoder = new TextDecoder()
289
 
290
  // 发送初始角色数据
291
  res.write(`data: ${JSON.stringify({
292
- id: `chatcmpl-${requestId}`,
293
  object: "chat.completion.chunk",
294
  created: timestamp,
295
  model: requestedModel,
@@ -307,7 +185,7 @@ async function handleStreamResponse(axiosResponse, res, requestedModel) {
307
  const textToSend = chunkStr
308
  if (textToSend) {
309
  res.write(`data: ${JSON.stringify({
310
- id: `chatcmpl-${requestId}`,
311
  object: "chat.completion.chunk",
312
  created: timestamp,
313
  model: requestedModel,
@@ -323,7 +201,7 @@ async function handleStreamResponse(axiosResponse, res, requestedModel) {
323
  axiosResponse.data.on('end', () => {
324
  // 发送完成信号
325
  res.write(`data: ${JSON.stringify({
326
- id: `chatcmpl-${requestId}`,
327
  object: "chat.completion.chunk",
328
  created: timestamp,
329
  model: requestedModel,
@@ -339,14 +217,19 @@ async function handleStreamResponse(axiosResponse, res, requestedModel) {
339
  })
340
 
341
  axiosResponse.data.on('error', (err) => {
 
 
 
 
 
342
  console.error('流数据处理错误:', err)
343
  res.status(500).end()
344
  })
345
  }
346
 
347
  // 处理非流数据
348
- function handleNonStreamResponse(axiosResponse, res, requestedModel) {
349
- const requestId = uuidv4()
350
  const timestamp = Math.floor(Date.now() / 1000)
351
 
352
  // 格式化为OpenAI的响应格式
@@ -357,20 +240,8 @@ function handleNonStreamResponse(axiosResponse, res, requestedModel) {
357
  content = content.slice(1, -1);
358
  }
359
 
360
- // 安全地处理内容,避免循环引用
361
- let safeContent = content
362
- if (!safeContent) {
363
- if (typeof axiosResponse.data === 'string') {
364
- safeContent = axiosResponse.data
365
- } else if (axiosResponse.data && axiosResponse.data.message) {
366
- safeContent = axiosResponse.data.message
367
- } else {
368
- safeContent = "响应内容为空"
369
- }
370
- }
371
-
372
  const responseData = {
373
- id: `chatcmpl-${requestId}`,
374
  object: "chat.completion",
375
  created: timestamp,
376
  model: requestedModel,
@@ -378,7 +249,7 @@ function handleNonStreamResponse(axiosResponse, res, requestedModel) {
378
  index: 0,
379
  message: {
380
  role: "assistant",
381
- content: safeContent
382
  },
383
  finish_reason: "stop"
384
  }],
@@ -423,116 +294,40 @@ async function retryWithNewCookie(req, res, config, currentCookie, requestedMode
423
 
424
  // OpenAI兼容的聊天完成API接口,使用中间件验证API密钥
425
  router.post('/completions', authenticate, async (req, res) => {
426
- const timestamp = new Date().toISOString()
427
- const requestId = require('uuid').v4().substring(0, 8)
428
 
429
  try {
430
  const openaiRequest = req.body
431
-
432
- // 检查请求体是否存在
433
- if (!openaiRequest) {
434
- console.error(`[${timestamp}] ❌ [${requestId}] 请求体为空`)
435
- return res.status(400).json({
436
- error: {
437
- message: '请求体为空',
438
- type: 'invalid_request_error',
439
- code: 'missing_body'
440
- }
441
- })
442
- }
443
-
444
  const isStreamRequest = openaiRequest.stream === true
445
  const requestedModel = openaiRequest.model
446
 
447
- console.log(`[${timestamp}] 🎯 [${requestId}] 开始处理聊天请求`)
448
- console.log(`[${timestamp}] 📊 [${requestId}] 模型: ${requestedModel}`)
449
- console.log(`[${timestamp}] 🌊 [${requestId}] 流式: ${isStreamRequest ? '是' : '否'}`)
450
- console.log(`[${timestamp}] 💬 [${requestId}] 消息数量: ${openaiRequest.messages?.length || 0}`)
451
-
452
- // 调试:输出原始请求体结构
453
- console.log(`[${timestamp}] 🐛 [${requestId}] 请求体类型: ${typeof openaiRequest}`)
454
- console.log(`[${timestamp}] 🐛 [${requestId}] 请求体键: ${Object.keys(openaiRequest || {}).join(', ')}`)
455
- if (openaiRequest.messages) {
456
- console.log(`[${timestamp}] 🐛 [${requestId}] 消息结构预览:`)
457
- openaiRequest.messages.forEach((msg, i) => {
458
- console.log(`[${timestamp}] 🐛 [${requestId}] 消息${i}: role=${msg.role}, content类型=${Array.isArray(msg.content) ? 'array' : typeof msg.content}`)
459
- })
460
-
461
- // 详细的图片格式检测
462
- debugImageFormats(openaiRequest.messages, requestId, timestamp)
463
- }
464
-
465
- // 检查是否包含图片 - 增强检测逻辑
466
- let hasImages = false
467
- let imageCount = 0
468
- console.log(`[${timestamp}] 🔍 [${requestId}] 开始检查消息中的图片`)
469
-
470
- if (openaiRequest.messages) {
471
- openaiRequest.messages.forEach((msg, msgIndex) => {
472
- console.log(`[${timestamp}] 📝 [${requestId}] 消息${msgIndex + 1} - 角色: ${msg.role}`)
473
- console.log(`[${timestamp}] 📝 [${requestId}] 消息${msgIndex + 1} - 内容类型: ${Array.isArray(msg.content) ? '数组' : typeof msg.content}`)
474
-
475
- if (Array.isArray(msg.content)) {
476
- msg.content.forEach((part, partIndex) => {
477
- console.log(`[${timestamp}] 🔍 [${requestId}] 消息${msgIndex + 1}-部分${partIndex + 1} - 类型: ${part.type}`)
478
- console.log(`[${timestamp}] 🔍 [${requestId}] 消息${msgIndex + 1}-部分${partIndex + 1} - 完整内容:`, JSON.stringify(part, null, 2))
479
-
480
- // 标准OpenAI格式检测
481
- if (part.type === 'image_url') {
482
- hasImages = true
483
- imageCount++
484
- console.log(`[${timestamp}] 🖼️ [${requestId}] 发现标准格式图片${imageCount}: ${part.image_url?.url ? part.image_url.url.substring(0, 50) + '...' : '无URL'}`)
485
- }
486
- // 检测其他可能的图片格式
487
- else if (part.type === 'image' || part.type === 'image_base64') {
488
- hasImages = true
489
- imageCount++
490
- console.log(`[${timestamp}] 🖼️ [${requestId}] 发现非标准格式图片${imageCount}: 类型=${part.type}`)
491
- }
492
- // 检测文本中是否包含base64图片
493
- else if (part.type === 'text' && part.text && typeof part.text === 'string' && part.text.includes('data:image/')) {
494
- hasImages = true
495
- imageCount++
496
- console.log(`[${timestamp}] 🖼️ [${requestId}] 在文本部分发现base64图片${imageCount}`)
497
- }
498
- // 检测是否有直接的图片数据字段
499
- else if (part.image || part.image_data || part.data) {
500
- hasImages = true
501
- imageCount++
502
- console.log(`[${timestamp}] 🖼️ [${requestId}] 发现直接图片数据${imageCount}`)
503
- }
504
- })
505
- } else if (typeof msg.content === 'string') {
506
- // 检查字符串内容是否包含base64图片
507
- if (msg.content.includes('data:image/')) {
508
- console.log(`[${timestamp}] 🖼️ [${requestId}] 在字符串消息中发���base64图片`)
509
- hasImages = true
510
- imageCount++
511
- }
512
- }
513
- })
514
- }
515
-
516
- console.log(`[${timestamp}] 📊 [${requestId}] 图片检查结果: ${hasImages ? `发现${imageCount}张图片` : '未发现图片'}`)
517
 
518
  // 设置适当的响应头
519
  if (isStreamRequest) {
520
  res.setHeader('Content-Type', 'text/event-stream')
521
  res.setHeader('Cache-Control', 'no-cache')
522
  res.setHeader('Connection', 'keep-alive')
523
- console.log(`[${timestamp}] 🌊 [${requestId}] 设置流式响应头`)
524
  }
525
 
526
  // 转换请求格式
527
- console.log(`[${timestamp}] 🔄 [${requestId}] 开始转换请求格式`)
528
- const form = await convertOpenAIToMammouth(openaiRequest)
529
- console.log(`[${timestamp}] ✅ [${requestId}] 请求格式转换完成`)
530
 
531
  // 获取Cookie - 根据模型类型使用不同的获取方法
532
  const cookieValue = isUnlimitedModel(requestedModel)
533
  ? accountManager.getAnyCookie()
534
  : accountManager.getNextAvailableCookie()
535
- console.log(`[${timestamp}] 🍪 [${requestId}] 使用账号: ${cookieValue ? cookieValue.substring(0, 8) + '...' : '无可用账号'}`)
 
 
 
536
 
537
  // 准备请求配置
538
  const config = {
@@ -549,82 +344,51 @@ router.post('/completions', authenticate, async (req, res) => {
549
 
550
  try {
551
  // 发送请求到Mammouth API
552
- console.log(`[${timestamp}] 🚀 [${requestId}] 发送请求到Mammouth API`)
553
- console.log(`[${timestamp}] 🌐 [${requestId}] 请求URL: ${config.url}`)
554
- console.log(`[${timestamp}] 📋 [${requestId}] 请求方法: ${config.method}`)
555
- console.log(`[${timestamp}] 🍪 [${requestId}] Cookie: ${cookieValue ? cookieValue.substring(0, 8) + '...' : '无'}`)
556
-
557
- // 记录FormData的基本信息(不记录具体内容避免日志过长)
558
- if (form && form.getHeaders) {
559
- const headers = form.getHeaders()
560
- console.log(`[${timestamp}] 📦 [${requestId}] FormData Content-Type: ${headers['content-type']?.substring(0, 50)}...`)
561
- }
562
-
563
  const response = await axios(config)
564
- console.log(`[${timestamp}] [${requestId}] Mammouth API响应成功`)
565
- console.log(`[${timestamp}] 📊 [${requestId}] 响应状态: ${response.status}`)
566
-
567
- // 安全地计算响应大小
568
- let responseSize = 'unknown'
569
- try {
570
- if (typeof response.data === 'string') {
571
- responseSize = response.data.length
572
- } else if (response.data && typeof response.data === 'object') {
573
- responseSize = 'object'
574
- }
575
- } catch (error) {
576
- responseSize = 'cannot_measure'
577
- }
578
- console.log(`[${timestamp}] 📏 [${requestId}] 响应大小: ${responseSize} 字符`)
579
 
580
  // 处理响应
581
  if (isStreamRequest) {
582
- console.log(`[${timestamp}] 🌊 [${requestId}] 开始处理流式响应`)
583
- handleStreamResponse(response, res, requestedModel)
584
  } else {
585
- console.log(`[${timestamp}] 📄 [${requestId}] 开始处理非流式响应`)
586
- handleNonStreamResponse(response, res, requestedModel)
587
  }
 
 
 
 
 
 
 
 
588
  } catch (error) {
 
 
 
 
589
  // 优化错误日志打印,只打印关键信息
590
  const errorStatus = error.response?.status || 'unknown'
591
  const errorMessage = error.response?.data?.message || error.message || 'Unknown error'
592
- const errorData = error.response?.data || 'No response data'
593
-
594
- console.error(`[${timestamp}] ❌ [${requestId}] API转发错误: [${errorStatus}] ${errorMessage}`)
595
- console.error(`[${timestamp}] 🔍 [${requestId}] 错误详情: ${error.stack?.split('\n')[0] || 'No stack trace'}`)
596
-
597
- // 安全地记录响应数据,避免循环引用
598
- try {
599
- if (typeof errorData === 'string') {
600
- console.error(`[${timestamp}] 📋 [${requestId}] 响应数据: ${errorData}`)
601
- } else if (errorData && typeof errorData === 'object') {
602
- // 只记录基本属性,避免循环引用
603
- const safeErrorData = {
604
- message: errorData.message,
605
- error: errorData.error,
606
- status: errorData.status,
607
- code: errorData.code
608
- }
609
- console.error(`[${timestamp}] 📋 [${requestId}] 响应数据:`, JSON.stringify(safeErrorData, null, 2))
610
- }
611
- } catch (jsonError) {
612
- console.error(`[${timestamp}] 📋 [${requestId}] 响应数据: [无法序列化]`)
613
- }
614
 
615
- console.error(`[${timestamp}] 🌐 [${requestId}] 请求URL: ${config.url}`)
616
- console.error(`[${timestamp}] 🍪 [${requestId}] 使用的Cookie: ${cookieValue ? cookieValue.substring(0, 8) + '...' : '无'}`)
 
 
 
 
 
617
 
618
- // 如果是图片相关的错误,记录图片信息
619
- if (hasImages) {
620
- console.error(`[${timestamp}] 🖼️ [${requestId}] 图片处理信息: 包含${imageCount}张图片`)
621
- }
622
 
623
  // 如果是403错误(达到使用限制)
624
  if (error.response && error.response.status === 403) {
625
  // console.log(error)
626
 
627
- console.log(`[${timestamp}] 🚫 [${requestId}] 账号 ${cookieValue.substring(0, 5)}... 使用模型 ${requestedModel} 已达到使用限制`)
628
 
629
  // 根据模型类型进行不同处理
630
  if (isUnlimitedModel(requestedModel)) {
@@ -741,6 +505,11 @@ router.post('/completions', authenticate, async (req, res) => {
741
  }
742
  } else {
743
  // 其他错误
 
 
 
 
 
744
  res.status(500).json({
745
  error: {
746
  message: '处理请求时发生错误',
@@ -751,6 +520,17 @@ router.post('/completions', authenticate, async (req, res) => {
751
  }
752
  }
753
  } catch (error) {
 
 
 
 
 
 
 
 
 
 
 
754
  console.error('请求处理错误:', error)
755
  res.status(500).json({
756
  error: {
 
5
  const { MODEL_MAPPING, MAMMOUTH_API_URL, AUTH_TOKEN, UNLIMITED_MODELS } = require('../config')
6
  const accountManager = require('../lib/manager')
7
  const imageUploader = require('../lib/uploader')
8
+ const logger = require('../lib/logger')
9
 
10
  const router = express.Router()
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  // API密钥认证中间件
13
  const authenticate = (req, res, next) => {
14
  const authHeader = req.headers.authorization || req.headers.Authorization || req.headers['x-api-key']
 
44
  }
45
 
46
  // 将OpenAI格式转换为Mammouth格式
47
+ async function convertOpenAIToMammouth(openaiRequest, requestId = null) {
48
  const form = new FormData()
49
 
50
  // 模型选择
 
69
  form.append('preprompt', preprompt)
70
 
71
  // 处理非system角色的消息
72
+ let totalImageCount = 0
73
+
74
+ // 先统计图片总数用于日志
75
+ regularMessages.forEach(message => {
76
+ if (Array.isArray(message.content)) {
77
+ totalImageCount += message.content.filter(part => part.type === 'image_url').length
78
+ }
79
+ })
80
+
81
+ if (requestId && totalImageCount > 0) {
82
+ logger.logImageProcessingStart(requestId, totalImageCount)
83
+ }
84
+
85
+ let currentImageIndex = 0
86
+
87
  for (const message of regularMessages) {
88
  // 处理包含图片的消息
89
  const imagesData = []
 
95
 
96
  // 遍历内容部分
97
  for (const part of message.content) {
 
 
98
  if (part.type === 'text') {
99
  textParts.push(part.text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  } else if (part.type === 'image_url') {
 
101
  try {
 
 
102
  // 获取图片数据
103
  let imageUrl = part.image_url
104
  if (typeof imageUrl === 'object' && imageUrl.url) {
105
  imageUrl = imageUrl.url
106
  }
107
 
108
+ // 使用智能上传方法处理图片(支持长图)
109
+ let uploadedUrls = []
 
 
110
  if (imageUrl.startsWith('data:image')) {
111
+ uploadedUrls = await imageUploader.uploadFromBase64Smart(
112
+ imageUrl,
113
+ null,
114
+ requestId,
115
+ currentImageIndex
116
+ )
 
 
 
 
117
  } else {
118
+ uploadedUrls = await imageUploader.uploadFromUrlSmart(
119
+ imageUrl,
120
+ null,
121
+ requestId,
122
+ currentImageIndex
123
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
125
+
126
+ // 将所有上传的URL添加到imagesData中
127
+ // 对于长图,这将包含多个片段的URL
128
+ imagesData.push(...uploadedUrls)
129
+
130
+ currentImageIndex++
131
  } catch (error) {
132
+ if (requestId) {
133
+ logger.logError(requestId, 'IMAGE_PROCESSING_ERROR', error.message, {
134
+ imageIndex: currentImageIndex,
135
+ imageUrl: typeof part.image_url === 'string' ? part.image_url.substring(0, 100) : 'object'
136
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  }
138
+ console.error('图片处理错误:', error.message)
139
+ currentImageIndex++
140
  }
141
  }
142
  }
 
154
  imagesData: imagesData,
155
  documentsData: []
156
  }))
 
 
157
  }
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  return form
160
  }
161
 
162
  // 处理流数据
163
+ async function handleStreamResponse(axiosResponse, res, requestedModel, logRequestId = null) {
164
+ const responseId = uuidv4()
165
  const timestamp = Math.floor(Date.now() / 1000)
166
  const decoder = new TextDecoder()
167
 
168
  // 发送初始角色数据
169
  res.write(`data: ${JSON.stringify({
170
+ id: `chatcmpl-${responseId}`,
171
  object: "chat.completion.chunk",
172
  created: timestamp,
173
  model: requestedModel,
 
185
  const textToSend = chunkStr
186
  if (textToSend) {
187
  res.write(`data: ${JSON.stringify({
188
+ id: `chatcmpl-${responseId}`,
189
  object: "chat.completion.chunk",
190
  created: timestamp,
191
  model: requestedModel,
 
201
  axiosResponse.data.on('end', () => {
202
  // 发送完成信号
203
  res.write(`data: ${JSON.stringify({
204
+ id: `chatcmpl-${responseId}`,
205
  object: "chat.completion.chunk",
206
  created: timestamp,
207
  model: requestedModel,
 
217
  })
218
 
219
  axiosResponse.data.on('error', (err) => {
220
+ if (logRequestId) {
221
+ logger.logError(logRequestId, 'STREAM_ERROR', err.message, {
222
+ model: requestedModel
223
+ })
224
+ }
225
  console.error('流数据处理错误:', err)
226
  res.status(500).end()
227
  })
228
  }
229
 
230
  // 处理非流数据
231
+ function handleNonStreamResponse(axiosResponse, res, requestedModel, logRequestId = null) {
232
+ const responseId = uuidv4()
233
  const timestamp = Math.floor(Date.now() / 1000)
234
 
235
  // 格式化为OpenAI的响应格式
 
240
  content = content.slice(1, -1);
241
  }
242
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  const responseData = {
244
+ id: `chatcmpl-${responseId}`,
245
  object: "chat.completion",
246
  created: timestamp,
247
  model: requestedModel,
 
249
  index: 0,
250
  message: {
251
  role: "assistant",
252
+ content: content || axiosResponse.data
253
  },
254
  finish_reason: "stop"
255
  }],
 
294
 
295
  // OpenAI兼容的聊天完成API接口,使用中间件验证API密钥
296
  router.post('/completions', authenticate, async (req, res) => {
297
+ let requestId = null
298
+ const startTime = Date.now()
299
 
300
  try {
301
  const openaiRequest = req.body
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  const isStreamRequest = openaiRequest.stream === true
303
  const requestedModel = openaiRequest.model
304
 
305
+ // 记录请求开始
306
+ requestId = logger.logRequestStart(
307
+ req.method,
308
+ req.originalUrl,
309
+ req.headers,
310
+ openaiRequest
311
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
  // 设置适当的响应头
314
  if (isStreamRequest) {
315
  res.setHeader('Content-Type', 'text/event-stream')
316
  res.setHeader('Cache-Control', 'no-cache')
317
  res.setHeader('Connection', 'keep-alive')
 
318
  }
319
 
320
  // 转换请求格式
321
+ const form = await convertOpenAIToMammouth(openaiRequest, requestId)
 
 
322
 
323
  // 获取Cookie - 根据模型类型使用不同的获取方法
324
  const cookieValue = isUnlimitedModel(requestedModel)
325
  ? accountManager.getAnyCookie()
326
  : accountManager.getNextAvailableCookie()
327
+
328
+ // 记录模型调用开始
329
+ const mammouthModel = MODEL_MAPPING[requestedModel] || requestedModel
330
+ logger.logModelCallStart(requestId, requestedModel, mammouthModel)
331
 
332
  // 准备请求配置
333
  const config = {
 
344
 
345
  try {
346
  // 发送请求到Mammouth API
347
+ const modelCallStartTime = Date.now()
 
 
 
 
 
 
 
 
 
 
348
  const response = await axios(config)
349
+ const modelCallDuration = Date.now() - modelCallStartTime
350
+
351
+ // 记录模型调用成功
352
+ logger.logModelCallEnd(requestId, true, null, modelCallDuration)
 
 
 
 
 
 
 
 
 
 
 
353
 
354
  // 处理响应
355
  if (isStreamRequest) {
356
+ handleStreamResponse(response, res, requestedModel, requestId)
 
357
  } else {
358
+ handleNonStreamResponse(response, res, requestedModel, requestId)
 
359
  }
360
+
361
+ // 记录请求成功结束
362
+ logger.logRequestEnd(requestId, 200, {
363
+ responseType: isStreamRequest ? 'stream' : 'json',
364
+ totalDuration: Date.now() - startTime
365
+ })
366
+
367
+ } catch (error) {
368
  } catch (error) {
369
+ // 记录模型调用失败
370
+ const modelCallDuration = Date.now() - startTime
371
+ logger.logModelCallEnd(requestId, false, error.message, modelCallDuration)
372
+
373
  // 优化错误日志打印,只打印关键信息
374
  const errorStatus = error.response?.status || 'unknown'
375
  const errorMessage = error.response?.data?.message || error.message || 'Unknown error'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
+ // 记录详细错误信息
378
+ logger.logError(requestId, 'MODEL_CALL_ERROR', errorMessage, {
379
+ status: errorStatus,
380
+ model: requestedModel,
381
+ isStream: isStreamRequest,
382
+ cookieUsed: cookieValue?.substring(0, 8) + '...'
383
+ })
384
 
385
+ console.error(`API转发错误: [${errorStatus}] ${errorMessage}`)
 
 
 
386
 
387
  // 如果是403错误(达到使用限制)
388
  if (error.response && error.response.status === 403) {
389
  // console.log(error)
390
 
391
+ console.log(`账号 ${cookieValue.substring(0, 5)}... 使用模型 ${requestedModel} 已达到使用限制`)
392
 
393
  // 根据模型类型进行不同处理
394
  if (isUnlimitedModel(requestedModel)) {
 
505
  }
506
  } else {
507
  // 其他错误
508
+ logger.logRequestEnd(requestId, 500, {
509
+ error: error.message,
510
+ totalDuration: Date.now() - startTime
511
+ })
512
+
513
  res.status(500).json({
514
  error: {
515
  message: '处理请求时发生错误',
 
520
  }
521
  }
522
  } catch (error) {
523
+ // 记录最终错误
524
+ if (requestId) {
525
+ logger.logError(requestId, 'REQUEST_ERROR', error.message, {
526
+ totalDuration: Date.now() - startTime
527
+ })
528
+ logger.logRequestEnd(requestId, 500, {
529
+ error: error.message,
530
+ totalDuration: Date.now() - startTime
531
+ })
532
+ }
533
+
534
  console.error('请求处理错误:', error)
535
  res.status(500).json({
536
  error: {