File size: 33,089 Bytes
f049116
 
 
 
 
 
 
86c3fd8
ed0c017
f049116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86c3fd8
f049116
 
 
 
 
 
 
d995792
 
 
 
 
 
 
 
faba1b4
 
 
 
 
d995792
 
f049116
 
 
 
 
 
 
 
 
 
 
 
 
89082f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f049116
 
 
86c3fd8
 
 
86e09a1
 
86c3fd8
86e09a1
 
 
 
 
 
86c3fd8
 
 
86e09a1
 
86c3fd8
 
 
 
 
 
f049116
 
 
d837981
f049116
 
 
 
d837981
f049116
d837981
f049116
 
 
 
d837981
 
 
f049116
d837981
 
 
23b00a5
 
e6b1cd3
23b00a5
e6b1cd3
23b00a5
0b715ff
 
db50d18
5cdca7f
 
0b715ff
86c3fd8
0b715ff
 
 
 
 
 
d837981
0b715ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d837981
0b715ff
d837981
0b715ff
 
 
ebc87b9
0b715ff
 
db50d18
 
0b715ff
89082f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d837981
0b715ff
89082f1
0b715ff
 
 
db50d18
89082f1
0b715ff
 
 
 
 
 
 
 
 
5cdca7f
0b715ff
 
db50d18
0b715ff
5cdca7f
 
0b715ff
 
 
 
 
 
 
d837981
0b715ff
 
 
 
 
 
 
 
 
 
 
311ba51
db50d18
0b715ff
5cdca7f
 
0b715ff
 
f049116
 
 
db50d18
 
0b715ff
e6b1cd3
0b715ff
db50d18
0b715ff
db50d18
 
0b715ff
 
db50d18
0b715ff
 
 
db50d18
 
0b715ff
db50d18
0b715ff
db50d18
 
0b715ff
e6b1cd3
0b715ff
 
db50d18
0b715ff
 
 
 
 
 
 
e6b1cd3
 
23b00a5
0b715ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23b00a5
 
 
e6b1cd3
23b00a5
 
 
0b715ff
 
23b00a5
 
d837981
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f049116
 
d837981
0b715ff
d837981
 
f049116
 
faba1b4
 
 
 
 
 
 
 
 
 
f049116
 
 
 
9026a2c
86c3fd8
f049116
 
 
091b154
 
faba1b4
 
 
091b154
 
f049116
091b154
86c3fd8
f049116
 
 
 
 
 
 
 
091b154
 
 
 
 
 
 
 
 
 
 
f049116
 
091b154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f049116
 
 
c00016a
091b154
 
 
faba1b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
091b154
faba1b4
d995792
091b154
 
 
 
9026a2c
 
d995792
faba1b4
d995792
9026a2c
 
 
 
d995792
9026a2c
d995792
 
 
 
 
 
9026a2c
d995792
 
 
 
9026a2c
 
 
 
faba1b4
d995792
9026a2c
 
faba1b4
091b154
 
 
f049116
091b154
86c3fd8
f049116
 
 
 
 
 
 
 
091b154
f049116
091b154
 
f049116
 
091b154
 
 
 
f049116
 
 
86c3fd8
 
091b154
 
 
86c3fd8
091b154
86c3fd8
f049116
 
 
 
 
 
86c3fd8
 
f049116
 
 
 
 
 
 
 
 
 
 
86c3fd8
f049116
 
 
 
 
 
 
86c3fd8
f049116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9026a2c
f049116
 
 
 
 
 
 
 
 
 
 
 
 
86c3fd8
 
a863a8a
f049116
 
 
 
 
86c3fd8
 
 
 
 
 
 
a863a8a
f049116
 
 
 
 
 
 
 
86c3fd8
f049116
 
 
 
 
86c3fd8
 
 
 
f049116
 
 
 
 
 
 
 
 
 
 
faba1b4
 
 
 
 
 
 
 
 
 
f049116
 
 
 
86c3fd8
f049116
86c3fd8
 
 
 
f049116
 
 
9026a2c
f049116
86c3fd8
f049116
86c3fd8
 
 
 
 
 
 
f049116
86c3fd8
 
 
 
f049116
 
 
d33bb2e
86c3fd8
 
 
 
 
 
 
35b6e22
86c3fd8
f049116
 
 
 
 
86c3fd8
f049116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9026a2c
f049116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed0c017
86c3fd8
 
 
 
 
ed0c017
f049116
 
 
ed0c017
86c3fd8
 
 
 
 
 
 
ed0c017
 
 
f049116
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
const express = require('express')
const axios = require('axios')
const FormData = require('form-data')
const { v4: uuidv4 } = require('uuid')
const { MODEL_MAPPING, MAMMOUTH_API_URL, AUTH_TOKEN, UNLIMITED_MODELS } = require('../config')
const accountManager = require('../lib/manager')
const imageUploader = require('../lib/uploader')
const logger = require('../lib/logger')
const ErrorHandler = require('../lib/errorHandler')

const router = express.Router()

// API密钥认证中间件
const authenticate = (req, res, next) => {
  const authHeader = req.headers.authorization || req.headers.Authorization || req.headers['x-api-key']

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({
      error: {
        message: '缺少有效的API密钥',
        type: 'authentication_error',
        code: 'invalid_api_key'
      }
    })
  }

  const apiKey = authHeader.substring(7)

  if (apiKey !== AUTH_TOKEN) {
    return res.status(401).json({
      error: {
        message: 'API密钥无效',
        type: 'authentication_error',
        code: 'invalid_api_key'
      }
    })
  }

  next()
}

// 检查模型是否在不受限制的列表中
function isUnlimitedModel(model) {
  return UNLIMITED_MODELS.includes(model)
}

// 将OpenAI格式转换为Mammouth格式
async function convertOpenAIToMammouth(openaiRequest, requestId = null) {
  const form = new FormData()

  // 模型选择
  const requestedModel = openaiRequest.model
  const mammouthModel = MODEL_MAPPING[requestedModel] || openaiRequest.model
  form.append('model', mammouthModel)

  // 添加流式响应参数(如果请求是流式的)
  if (openaiRequest.stream === true) {
    form.append('stream', 'true')
    form.append('streaming', 'true')

    if (requestId) {
      console.log(`[请求转换] 添加流式响应参数: stream=true, streaming=true`)
    }
  } else {
    // 确保非流式请求不包含流式参数
    if (requestId) {
      console.log(`[请求转换] 非流式请求,不添加流式参数`)
    }
  }

  // 提取system角色的消息作为preprompt
  let systemMessages = []
  let regularMessages = []

  openaiRequest.messages.forEach(message => {
    if (message.role === 'system') {
      systemMessages.push(message.content)
    } else {
      regularMessages.push(message)
    }
  })

  // 将所有system消息组合为preprompt
  let preprompt = systemMessages.join('\n\n')

  // 检查是否有长图需要处理,如果有则添加长图处理说明
  const hasLongImages = regularMessages.some(message =>
    Array.isArray(message.content) &&
    message.content.some(part => part.type === 'image_url')
  )

  if (hasLongImages) {
    const longImagePrompt = `



重要说明:本次对话可能包含长图片段。当你看到标记为"[长图片段 X/Y]"的图片时:

1. 这些是同一张长图的不同部分,按顺序排列

2. 请分析每个片段的内容,记住之前片段的信息

3. 在处理最后一个片段时,请提供基于所有片段的完整分析

4. 确保回答涵盖整张长图的所有重要内容,不要遗漏任何部分`

    preprompt = preprompt ? `${preprompt}${longImagePrompt}` : longImagePrompt.trim()
  }

  form.append('preprompt', preprompt)

  // 处理非system角色的消息
  let totalImageCount = 0

  // 先统计图片总数用于日志
  regularMessages.forEach((message, index) => {
    console.log(`[调试] 消息${index}内容类型:`, typeof message.content, Array.isArray(message.content) ? '数组' : '非数组')
    if (Array.isArray(message.content)) {
      const imageCount = message.content.filter(part => part.type === 'image_url').length
      totalImageCount += imageCount
      console.log(`[调试] 消息${index}包含${imageCount}张图片`)
      message.content.forEach((part, partIndex) => {
        console.log(`[调试] 消息${index}部分${partIndex}类型:`, part.type)
      })
    }
  })

  console.log(`[调试] 总图片数量: ${totalImageCount}`)

  if (requestId && totalImageCount > 0) {
    logger.logImageProcessingStart(requestId, totalImageCount)
  }

  let currentImageIndex = 0

  for (const message of regularMessages) {
    // 处理包含图片的消息
    let content = message.content
    let processedMessages = [] // 存储处理后的消息(可能包含多个片段)

    // 如果是对象数组(多模态内容)
    if (Array.isArray(message.content)) {
      const textParts = []
      const imageParts = []

      // 分离文本和图片部分
      for (const part of message.content) {
        if (part.type === 'text') {
          textParts.push(part.text)
        } else if (part.type === 'image_url') {
          imageParts.push(part)
        }
      }

      // 合并所有文本部分
      const combinedText = textParts.join('\n')

      // 分别收集长图和普通图片
      const longImageSegments = [] // 存储长图片段
      const normalImageResults = [] // 存储普通图片结果,按索引排序
      let hasProcessedText = false // 标记是否已处理文本
      let normalImageCount = 0 // 普通图片计数

      // 处理每个图片 - 严格按顺序处理,确保不会出现顺序混乱
      console.log(`[图片处理开始] 共${imageParts.length}张图片待处理,将严格按顺序处理`)

      for (let imagePartIndex = 0; imagePartIndex < imageParts.length; imagePartIndex++) {
        const imagePart = imageParts[imagePartIndex]
        console.log(`[图片处理] 开始处理第${imagePartIndex + 1}张图片 (消息位置: ${imagePartIndex}, 全局索引: ${currentImageIndex + 1})`)

        try {
          // 获取图片数据
          let imageUrl = imagePart.image_url
          if (typeof imageUrl === 'object' && imageUrl.url) {
            imageUrl = imageUrl.url
          }

          console.log(`[图片处理] 图片${currentImageIndex + 1}类型: ${imageUrl.startsWith('data:image') ? 'Base64' : 'URL'}`)

          // 使用智能上传方法处理图片(支持长图)
          let uploadedUrls = []
          if (imageUrl.startsWith('data:image')) {
            uploadedUrls = await imageUploader.uploadFromBase64Smart(
              imageUrl,
              null,
              requestId,
              currentImageIndex,
              false  // 恢复正常缓存机制
            )
          } else {
            uploadedUrls = await imageUploader.uploadFromUrlSmart(
              imageUrl,
              null,
              requestId,
              currentImageIndex,
              false  // 恢复正常缓存机制
            )
          }

          console.log(`[图片处理] 图片${currentImageIndex + 1}上传完成,获得${uploadedUrls.length}个URL,位置索引: ${imagePartIndex}`)

          // 如果是长图(多个片段),为每个片段创建单独的消息
          if (uploadedUrls.length > 1) {
            console.log(`[长图处理] 图片${currentImageIndex + 1}被切割为${uploadedUrls.length}个片段,将按顺序发送`)

            if (requestId) {
              logger.logMessageSegmentation(requestId, currentImageIndex, uploadedUrls.length)
            }

            uploadedUrls.forEach((url, segmentIndex) => {
              // 为每个片段生成更详细的提示文本
              let segmentText = ''

              if (segmentIndex === 0) {
                // 第一个片段:包含原始文本和长图说明
                const originalText = combinedText || '请分析这张长图的内容'
                segmentText = `${originalText}



注意:这是一张长图,已被切割为${uploadedUrls.length}个片段。请分析每个片段的内容,并在最后一个片段时提供完整的总结。



[长图片段 ${segmentIndex + 1}/${uploadedUrls.length}] - 这是长图的开始部分`
                hasProcessedText = true
              } else if (segmentIndex === uploadedUrls.length - 1) {
                // 最后一个片段:要求提供完整总结
                segmentText = `[长图片段 ${segmentIndex + 1}/${uploadedUrls.length}] - 这是长图的结束部分



请基于所有${uploadedUrls.length}个片段的内容,提供这张长图的完整分析和总结。`
              } else {
                // 中间片段:说明这是连续内容
                segmentText = `[长图片段 ${segmentIndex + 1}/${uploadedUrls.length}] - 这是长图的中间部分,请继续分析内容`
              }

              processedMessages.push({
                content: segmentText,
                imagesData: [url],
                documentsData: []
              })

              console.log(`[消息生成] 长图片段${segmentIndex + 1}: "${segmentText.substring(0, 80)}..."`)
            })
          } else {
            // 普通图片,严格按顺序存储到结果数组中
            const imageResult = {
              index: imagePartIndex,           // 在消息中的位置索引(关键排序字段)
              urls: uploadedUrls,
              originalIndex: currentImageIndex, // 全局图片索引
              processOrder: normalImageCount    // 处理顺序
            }

            normalImageResults.push(imageResult)
            normalImageCount++

            console.log(`[图片收集] 普通图片${currentImageIndex + 1}已收集 (消息位置: ${imagePartIndex}, 处理顺序: ${normalImageCount}),当前共${normalImageCount}张普通图片`)
          }

          currentImageIndex++
        } catch (error) {
          if (requestId) {
            logger.logError(requestId, 'IMAGE_PROCESSING_ERROR', error.message, {
              imageIndex: currentImageIndex,
              imagePartIndex: imagePartIndex,
              imageUrl: typeof imagePart.image_url === 'string' ? imagePart.image_url.substring(0, 100) : 'object'
            })
          }
          console.error(`图片处理错误 (位置${imagePartIndex}, 全局${currentImageIndex + 1}):`, error.message)

          // 图片处理失败时,添加一个错误占位符,避免完全跳过
          const errorPlaceholder = {
            index: imagePartIndex,
            urls: [],
            originalIndex: currentImageIndex,
            processOrder: normalImageCount,
            error: true,
            errorMessage: error.message
          }

          normalImageResults.push(errorPlaceholder)
          normalImageCount++

          console.log(`[图片错误] 图片${currentImageIndex + 1}处理失败,已添加错误占位符`)
          currentImageIndex++
        }
      }

      console.log(`[图片处理完成] 共处理${imageParts.length}张图片,成功收集${normalImageResults.length}张普通图片`)

      // 如果有普通图片,严格按原始顺序创建一个包含所有普通图片的消息
      if (normalImageResults.length > 0) {
        console.log(`[排序前验证] 收集到${normalImageResults.length}张普通图片`)
        normalImageResults.forEach((result, idx) => {
          console.log(`  图片${idx + 1}: 消息位置=${result.index}, 全局索引=${result.originalIndex}, 处理顺序=${result.processOrder}`)
        })

        // 严格按照imagePartIndex排序,确保完全按客户端上传顺序
        console.log(`[开始排序] 严格按消息中的位置索引排序...`)
        normalImageResults.sort((a, b) => {
          const diff = a.index - b.index
          console.log(`[排序比较] 位置${a.index} vs 位置${b.index} = ${diff}`)
          return diff
        })

        console.log(`[排序后验证] 最终图片顺序:`)
        normalImageResults.forEach((result, idx) => {
          console.log(`  第${idx + 1}位: 消息位置=${result.index}, 全局索引=${result.originalIndex}, 处理顺序=${result.processOrder}`)
        })

        // 提取所有URL,严格保持顺序,跳过错误的图片
        const orderedImageUrls = []
        const errorMessages = []

        normalImageResults.forEach((result, idx) => {
          if (result.error) {
            console.log(`[URL提取] 第${idx + 1}个结果,位置${result.index},图片处理失败: ${result.errorMessage}`)
            errorMessages.push(`图片${result.originalIndex + 1}处理失败`)
          } else {
            console.log(`[URL提取] 第${idx + 1}个结果,位置${result.index},添加${result.urls.length}个URL`)
            orderedImageUrls.push(...result.urls)
          }
        })

        const includeOriginalText = !hasProcessedText && combinedText
        let normalImageText = includeOriginalText ? combinedText : ''

        // 构建图片状态信息
        const successCount = orderedImageUrls.length
        const errorCount = errorMessages.length

        if (successCount > 0 && errorCount > 0) {
          const statusText = `[包含 ${successCount} 张图片,${errorCount} 张图片处理失败]`
          normalImageText = normalImageText ? `${normalImageText}\n\n${statusText}` : statusText
        } else if (successCount > 0) {
          const statusText = `[包含 ${successCount} 张图片]`
          normalImageText = normalImageText ? `${normalImageText}\n\n${statusText}` : statusText
        } else if (errorCount > 0) {
          const statusText = `[${errorCount} 张图片处理失败]`
          normalImageText = normalImageText ? `${normalImageText}\n\n${statusText}` : statusText
        }

        processedMessages.push({
          content: normalImageText || '.',
          imagesData: orderedImageUrls,
          documentsData: []
        })

        console.log(`[消息生成] 普通图片批量消息: ${successCount}张成功,${errorCount}张失败,严格按顺序排列`)
        console.log(`[最终顺序验证] 图片顺序: ${normalImageResults.map(r => `位置${r.index}(图片${r.originalIndex + 1}${r.error ? '-失败' : ''})`).join(' -> ')}`)
      }

      // 如果没有图片,只有文本
      if (imageParts.length === 0) {
        processedMessages.push({
          content: combinedText || '.',
          imagesData: [],
          documentsData: []
        })
      }
    } else {
      // 纯文本消息
      processedMessages.push({
        content: content || '.',
        imagesData: [],
        documentsData: []
      })
    }

    // 将所有处理后的消息添加到表单
    processedMessages.forEach(msg => {
      form.append('messages', JSON.stringify(msg))
    })
  }

  // 统计总消息数量并记录警告
  const totalMessages = form.getBuffer().toString().split('messages').length - 1
  if (requestId && totalMessages > 4) {
    console.warn(`[消息转换] 警告:消息数量较多(${totalMessages}个),可能影响流式响应性能`)
    logger.logError(requestId, 'HIGH_MESSAGE_COUNT', `消息数量过多: ${totalMessages}个`, {
      totalMessages,
      recommendation: '考虑减少长图片段数量或使用非流式模式'
    })
  }

  return form
}

// 处理流数据
async function handleStreamResponse(axiosResponse, res, requestedModel, logRequestId = null) {
  const responseId = uuidv4()
  const timestamp = Math.floor(Date.now() / 1000)
  const decoder = new TextDecoder()

  if (logRequestId) {
    console.log(`[流式响应] 开始处理流式响应,请求ID: ${logRequestId}`)
    console.log(`[流式响应] 响应状态码: ${axiosResponse.status}`)
    console.log(`[流式响应] 响应头: ${JSON.stringify(axiosResponse.headers)}`)
    console.log(`[流式响应] 数据流类型: ${typeof axiosResponse.data}`)
  }

  // 发送初始角色数据
  const initialData = {
    id: `chatcmpl-${responseId}`,
    object: "chat.completion.chunk",
    created: timestamp,
    model: requestedModel,
    choices: [{
      index: 0,
      delta: { role: "assistant", content: "" },
      finish_reason: null
    }]
  }

  const initialMessage = `data: ${JSON.stringify(initialData)}\n\n`
  res.write(initialMessage)

  if (logRequestId) {
    console.log(`[流式响应] 已发送初始角色数据`)
  }

  let totalChunks = 0
  let totalContentLength = 0

  axiosResponse.data.on('data', (chunk) => {
    totalChunks++

    try {
      const chunkStr = decoder.decode(chunk, { stream: true })

      if (logRequestId) {
        console.log(`[流式响应] 收到数据块 ${totalChunks},原始长度: ${chunk.length},解码后长度: ${chunkStr.length}`)
        console.log(`[流式响应] 数据块内容预览: "${chunkStr.substring(0, 200)}${chunkStr.length > 200 ? '...' : ''}"`)
      }

      // 检查是否为有效的文本内容
      const textToSend = chunkStr.trim()
      if (textToSend && textToSend.length > 0) {
        totalContentLength += textToSend.length

        const responseData = {
          id: `chatcmpl-${responseId}`,
          object: "chat.completion.chunk",
          created: timestamp,
          model: requestedModel,
          choices: [{
            index: 0,
            delta: { content: textToSend },
            finish_reason: null
          }]
        }

        const responseMessage = `data: ${JSON.stringify(responseData)}\n\n`
        res.write(responseMessage)

        if (logRequestId) {
          console.log(`[流式响应] 已发送内容块 ${totalChunks},内容长度: ${textToSend.length}`)
        }
      } else {
        if (logRequestId) {
          console.log(`[流式响应] 数据块 ${totalChunks} 为空或无效,跳过发送`)
        }
      }
    } catch (decodeError) {
      if (logRequestId) {
        console.error(`[流式响应] 数据块 ${totalChunks} 解码失败: ${decodeError.message}`)
        logger.logError(logRequestId, 'STREAM_DECODE_ERROR', decodeError.message, {
          chunkLength: chunk.length,
          chunkNumber: totalChunks
        })
      }
    }
  })

  axiosResponse.data.on('end', async () => {
    if (logRequestId) {
      console.log(`[流式响应] 数据流结束,总共处理 ${totalChunks} 个数据块,总内容长度: ${totalContentLength}`)

      // 如果没有收到任何内容,检查是否API返回了非流式响应
      if (totalChunks === 0) {
        console.warn(`[流式响应] 警告:未收到任何数据块,可能API返回了非流式响应`)

        // 尝试检查响应是否是JSON格式
        try {
          if (axiosResponse.data && typeof axiosResponse.data === 'object' && axiosResponse.data.choices) {
            console.log(`[流式回退] 检测到非流式JSON响应,尝试转换为流式格式`)
            const content = axiosResponse.data.choices[0]?.message?.content || ''

            if (content) {
              // 将非流式响应转换为流式格式发送
              const chunkSize = 20
              for (let i = 0; i < content.length; i += chunkSize) {
                const chunk = content.substring(i, i + chunkSize)

                const chunkData = {
                  id: `chatcmpl-${responseId}`,
                  object: "chat.completion.chunk",
                  created: timestamp,
                  model: requestedModel,
                  choices: [{
                    index: 0,
                    delta: { content: chunk },
                    finish_reason: null
                  }]
                }

                const chunkMessage = `data: ${JSON.stringify(chunkData)}\n\n`
                res.write(chunkMessage)

                await new Promise(resolve => setTimeout(resolve, 50))
              }

              console.log(`[流式回退] 成功转换非流式响应为流式格式,内容长度: ${content.length}`)
              totalContentLength = content.length
            }
          }
        } catch (parseError) {
          console.error(`[流式回退] 解析非流式响应失败: ${parseError.message}`)
        }
      }

      // 如果仍然没有收到任何内容,使用默认回退机制
      if (totalContentLength === 0) {
        console.warn(`[流式响应] 警告:未收到任何内容,使用默认回退机制`)
        logger.logError(logRequestId, 'STREAM_NO_CONTENT', '流式响应未收到任何内容,尝试回退', {
          totalChunks,
          model: requestedModel
        })

        // 发送默认回退消息,避免复杂的HTTP请求导致连接问题
        console.log(`[流式回退] 使用默认消息回退`)

        const fallbackMessage = "抱歉,图片处理完成但响应出现问题。\n\n这可能是由于长图切割导致的流式响应问题。建议:\n1. 重新发送请求\n2. 使用非流式模式\n3. 或尝试上传较短的图片"

        // 将回退消息分块发送,模拟流式效果
        const chunkSize = 20
        for (let i = 0; i < fallbackMessage.length; i += chunkSize) {
          const chunk = fallbackMessage.substring(i, i + chunkSize)

          const chunkData = {
            id: `chatcmpl-${responseId}`,
            object: "chat.completion.chunk",
            created: timestamp,
            model: requestedModel,
            choices: [{
              index: 0,
              delta: { content: chunk },
              finish_reason: null
            }]
          }

          const chunkMessage = `data: ${JSON.stringify(chunkData)}\n\n`
          res.write(chunkMessage)

          // 添加小延迟模拟流式效果
          await new Promise(resolve => setTimeout(resolve, 50))
        }

        console.log(`[流式回退] 默认消息发送完成`)
        totalContentLength = fallbackMessage.length
      }
    }

    // 发送完成信号
    const endData = {
      id: `chatcmpl-${responseId}`,
      object: "chat.completion.chunk",
      created: timestamp,
      model: requestedModel,
      choices: [{
        index: 0,
        delta: {},
        finish_reason: "stop"
      }]
    }

    const endMessage = `data: ${JSON.stringify(endData)}\n\n`
    res.write(endMessage)
    res.write('data: [DONE]\n\n')
    res.end()

    if (logRequestId) {
      console.log(`[流式响应] 已发送完成信号和结束标记`)
    }
  })

  axiosResponse.data.on('error', (err) => {
    if (logRequestId) {
      logger.logError(logRequestId, 'STREAM_ERROR', err.message, {
        model: requestedModel,
        totalChunks,
        totalContentLength
      })
      console.log(`[流式响应] 流处理错误: ${err.message},已处理 ${totalChunks} 个数据块`)
    }
    console.error('流数据处理错误:', err)
    res.status(500).end()
  })
}

// 处理非流数据
function handleNonStreamResponse(axiosResponse, res, requestedModel, logRequestId = null) {
  const responseId = uuidv4()
  const timestamp = Math.floor(Date.now() / 1000)

  // 格式化为OpenAI的响应格式
  let content = axiosResponse.data.content;

  // 如果内容是字符串且被引号包裹,移除外层引号
  if (typeof content === 'string' && content.startsWith('"') && content.endsWith('"')) {
    content = content.slice(1, -1);
  }

  const responseData = {
    id: `chatcmpl-${responseId}`,
    object: "chat.completion",
    created: timestamp,
    model: requestedModel,
    choices: [{
      index: 0,
      message: {
        role: "assistant",
        content: content || axiosResponse.data
      },
      finish_reason: "stop"
    }],
    usage: {
      prompt_tokens: 0,
      completion_tokens: 0,
      total_tokens: 0
    }
  }

  res.json(responseData)
}

// 使用新的Cookie重新发送请求
async function retryWithNewCookie(req, res, config, currentCookie, requestedModel, isStreamRequest) {
  try {
    // 标记当前Cookie为不可用
    accountManager.markAsUnavailable(currentCookie)

    // 获取新的Cookie
    const newCookie = accountManager.getNextAvailableCookie()

    // 更新请求配置中的Cookie
    config.headers.Cookie = `auth_session=${newCookie}`

    // 发送请求到Mammouth API
    const response = await axios(config)

    // 处理响应
    if (isStreamRequest) {
      handleStreamResponse(response, res, requestedModel)
    } else {
      handleNonStreamResponse(response, res, requestedModel)
    }

    return true
  } catch (error) {
    // 如果重试也失败了,返回false
    return false
  }
}

// OpenAI兼容的聊天完成API接口,使用中间件验证API密钥
router.post('/completions', authenticate, async (req, res) => {
  let requestId = null
  const startTime = Date.now()

  try {
    const openaiRequest = req.body
    const isStreamRequest = openaiRequest.stream === true
    const requestedModel = openaiRequest.model

    // 记录请求开始
    requestId = logger.logRequestStart(
      req.method,
      req.originalUrl,
      req.headers,
      openaiRequest
    )

    // 设置适当的响应头
    if (isStreamRequest) {
      res.setHeader('Content-Type', 'text/event-stream')
      res.setHeader('Cache-Control', 'no-cache')
      res.setHeader('Connection', 'keep-alive')
    }

    // 转换请求格式
    const form = await convertOpenAIToMammouth(openaiRequest, requestId)

    // 获取Cookie - 根据模型类型使用不同的获取方法
    const cookieValue = isUnlimitedModel(requestedModel)
      ? accountManager.getAnyCookie()
      : accountManager.getNextAvailableCookie()

    // 记录模型调用开始
    const mammouthModel = MODEL_MAPPING[requestedModel] || requestedModel
    logger.logModelCallStart(requestId, requestedModel, mammouthModel)

    // 准备请求配置
    const config = {
      method: 'post',
      url: MAMMOUTH_API_URL,
      headers: {
        ...form.getHeaders(),
        'Cookie': `auth_session=${cookieValue}`,
        'origin': 'https://mammouth.ai'
      },
      data: form,
      responseType: isStreamRequest ? 'stream' : 'json',
      // 添加流式请求的额外配置
      ...(isStreamRequest && {
        timeout: 60000, // 60秒超时
        maxRedirects: 0 // 禁用重定向
      })
    }

    if (requestId && isStreamRequest) {
      console.log(`[请求配置] 流式请求配置: responseType=stream, timeout=60s`)
    }

    try {
      // 发送请求到Mammouth API
      const modelCallStartTime = Date.now()
      const response = await axios(config)
      const modelCallDuration = Date.now() - modelCallStartTime

      // 记录模型调用成功
      logger.logModelCallEnd(requestId, true, null, modelCallDuration)

      // 处理响应
      if (isStreamRequest) {
        handleStreamResponse(response, res, requestedModel, requestId)
      } else {
        handleNonStreamResponse(response, res, requestedModel, requestId)
      }

      // 记录请求成功结束
      logger.logRequestEnd(requestId, 200, {
        responseType: isStreamRequest ? 'stream' : 'json',
        totalDuration: Date.now() - startTime
      })

    } catch (error) {
      // 记录模型调用失败
      const modelCallDuration = Date.now() - startTime
      logger.logModelCallEnd(requestId, false, error.message, modelCallDuration)

      // 优化错误日志打印,只打印关键信息
      const errorStatus = error.response?.status || 'unknown'
      const errorMessage = error.response?.data?.message || error.message || 'Unknown error'

      // 记录详细错误信息
      logger.logError(requestId, 'MODEL_CALL_ERROR', errorMessage, {
        status: errorStatus,
        model: requestedModel,
        isStream: isStreamRequest,
        cookieUsed: cookieValue?.substring(0, 8) + '...'
      })

      console.error(`API转发错误: [${errorStatus}] ${errorMessage}`)

      // 如果是403错误(达到使用限制)
      if (error.response && error.response.status === 403) {
        // console.log(error)

        console.log(`账号 ${cookieValue.substring(0, 5)}... 使用模型 ${requestedModel} 已达到使用限制`)

        // 根据模型类型进行不同处理
        if (isUnlimitedModel(requestedModel)) {
          // 不受限模型也返回403,尝试将当前账号标记为不可用并再试一次
          accountManager.markAsUnavailable(cookieValue)

          // 对于不受限模型再次获取一个任意Cookie尝试
          const newCookie = accountManager.getAnyCookie()
          console.log(`尝试使用不受限模型的另一个账号: ${newCookie.substring(0, 5)}...`)

          // 更新配置
          config.headers.Cookie = `auth_session=${newCookie}`

          try {
            // 再次尝试请求
            const response = await axios(config)

            // 处理响应
            if (isStreamRequest) {
              handleStreamResponse(response, res, requestedModel)
            } else {
              handleNonStreamResponse(response, res, requestedModel)
            }

            // 成功,直接返回
            return
          } catch (retryError) {
            console.error(`无限制模型二次尝试也失败: ${retryError.message}`)
            // 继续到错误处理
          }
        } else {
          // 普通模型,尝试切换账号
          console.log(`尝试使用新账号...`)
          const cookieRetrySuccess = await retryWithNewCookie(
            req, res, config, cookieValue, requestedModel, isStreamRequest
          )

          // 如果切换账号成功,就返回
          if (cookieRetrySuccess) return
        }

        // 所有重试方法都失败,返回错误信息
        const errorMessage =
          error.response.data?.message ||
          error.response.data?.statusMessage ||
          '使用限制:所有账号已临时达到使用限制。请稍后再试。'

        const requestId = uuidv4()
        const timestamp = Math.floor(Date.now() / 1000)

        if (isStreamRequest) {
          // 流式响应情况下,以SSE格式返回错误消息
          res.write(`data: ${JSON.stringify({

            id: `chatcmpl-${requestId}`,

            object: "chat.completion.chunk",

            created: timestamp,

            model: requestedModel,

            choices: [{

              index: 0,

              delta: { role: "assistant", content: "" },

              finish_reason: null

            }]

          })}\n\n`)

          // 发送错误消息内容
          res.write(`data: ${JSON.stringify({

            id: `chatcmpl-${requestId}`,

            object: "chat.completion.chunk",

            created: timestamp,

            model: requestedModel,

            choices: [{

              index: 0,

              delta: { content: errorMessage },

              finish_reason: null

            }]

          })}\n\n`)

          // 发送完成信号
          res.write(`data: ${JSON.stringify({

            id: `chatcmpl-${requestId}`,

            object: "chat.completion.chunk",

            created: timestamp,

            model: requestedModel,

            choices: [{

              index: 0,

              delta: {},

              finish_reason: "stop"

            }]

          })}\n\n`)

          res.write('data: [DONE]\n\n')
          res.end()
        } else {
          // 非流式响应情况下,以普通JSON格式返回错误消息
          res.json({
            id: `chatcmpl-${requestId}`,
            object: "chat.completion",
            created: timestamp,
            model: requestedModel,
            choices: [{
              index: 0,
              message: {
                role: "assistant",
                content: errorMessage
              },
              finish_reason: "stop"
            }],
            usage: {
              prompt_tokens: 0,
              completion_tokens: 0,
              total_tokens: 0
            }
          })
        }
      } else {
        // 其他错误,使用统一错误处理
        logger.logRequestEnd(requestId, 500, {
          error: error.message,
          totalDuration: Date.now() - startTime
        })

        ErrorHandler.handleModelError(res, error, requestId, requestedModel, isStreamRequest)
      }
    }
  } catch (error) {
    // 使用统一错误处理
    if (requestId) {
      logger.logRequestEnd(requestId, 500, {
        error: error.message,
        totalDuration: Date.now() - startTime
      })
    }

    ErrorHandler.handleApiError(res, error, requestId, {
      totalDuration: Date.now() - startTime,
      endpoint: '/v1/chat/completions'
    })
  }
})


module.exports = router