sanbo1200 commited on
Commit
83bfd5a
·
verified ·
1 Parent(s): d48b38e

Update main.go

Browse files
Files changed (1) hide show
  1. main.go +417 -1147
main.go CHANGED
@@ -8,26 +8,23 @@ import (
8
  "io"
9
  "log"
10
  "net/http"
11
- "os"
12
  "regexp"
13
  "strings"
14
  "sync"
15
  "time"
16
  )
17
 
18
- // 配置变量(从环境变量读取)
19
  var (
20
- UPSTREAM_URL string
21
- DEFAULT_KEY string
22
- ZAI_TOKEN string
23
- MODEL_NAME string // 未使用,因为现在动态获取
24
- PORT string
25
- DEBUG_MODE bool
26
- DEFAULT_STREAM bool
27
- DASHBOARD_ENABLED bool
28
- ENABLE_THINKING bool
29
- MODELS_URL string // 新增:模型列表URL
30
- DEFAULT_UPSTREAM_MODEL_ID string // 新增:默认上游模型ID
31
  )
32
 
33
  // 请求统计信息
@@ -50,32 +47,40 @@ type LiveRequest struct {
50
  UserAgent string `json:"user_agent"`
51
  }
52
 
53
- // 上游模型响应结构 (新增)
54
- type UpstreamModelsResponse struct {
55
- Object string `json:"object"`
56
- Data []UpstreamModel `json:"data"`
57
- }
 
 
 
58
 
59
- type UpstreamModel struct {
60
- ID string `json:"id"`
61
- Name string `json:"name"`
62
- Object string `json:"object"`
63
- Created int64 `json:"created"`
64
- OwnedBy string `json:"owned_by"`
65
- Info struct {
66
- IsActive bool `json:"is_active"`
67
- CreatedAt int64 `json:"created_at"`
68
- } `json:"info"`
69
- }
 
 
 
 
 
 
70
 
71
  // OpenAI 请求结构
72
  type OpenAIRequest struct {
73
- Model string `json:"model"`
74
- Messages []Message `json:"messages"`
75
- Stream bool `json:"stream,omitempty"`
76
- Temperature float64 `json:"temperature,omitempty"`
77
- MaxTokens int `json:"max_tokens,omitempty"`
78
- EnableThinking *bool `json:"enable_thinking,omitempty"`
79
  }
80
 
81
  type Message struct {
@@ -133,18 +138,17 @@ type Usage struct {
133
 
134
  // 上游SSE响应结构
135
  type UpstreamData struct {
136
- Type string `json:"type"`
137
- Data struct {
138
- DeltaContent string `json:"delta_content"`
139
  Phase string `json:"phase"`
 
140
  Done bool `json:"done"`
141
- Usage Usage `json:"usage,omitempty"`
142
  Error *UpstreamError `json:"error,omitempty"`
143
  Inner *struct {
144
  Error *UpstreamError `json:"error,omitempty"`
145
- } `json:"data,omitempty"`
146
  } `json:"data"`
147
- Error *UpstreamError `json:"error,omitempty"`
148
  }
149
 
150
  type UpstreamError struct {
@@ -159,72 +163,41 @@ type ModelsResponse struct {
159
  }
160
 
161
  type Model struct {
162
- ID string `json:"id"` // 保持ID字段
163
  Object string `json:"object"`
164
- Name string `json:"name"` // 新增Name字段,用于显示
165
  Created int64 `json:"created"`
166
  OwnedBy string `json:"owned_by"`
167
  }
168
 
169
- // 全局变量
170
- var (
171
- stats RequestStats
172
- liveRequests = []LiveRequest{} // 初始化为空数组,而不是 nil
173
- statsMutex sync.Mutex
174
- requestsMutex sync.Mutex
175
- modelsCache []Model // 新增:缓存模型列表
176
- modelsMutex sync.RWMutex // 新增:保护模型缓存的读写锁
177
- )
178
-
179
- // 思考内容处理策略
180
- const (
181
- THINK_TAGS_MODE = "strip" // strip: 去除<details>标签;think: 转为<think>标签;raw: 保留原样
182
- )
183
-
184
- // 伪装前端头部(来自抓包)
185
- const (
186
- X_FE_VERSION = "prod-fe-1.0.70"
187
- BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0"
188
- SEC_CH_UA = "\"Not;A=Brand\";v=\"99\", \"Microsoft Edge\";v=\"139\", \"Chromium\";v=\"139\""
189
- SEC_CH_UA_MOB = "?0"
190
- SEC_CH_UA_PLAT = "\"Windows\""
191
- ORIGIN_BASE = "https://chat.z.ai"
192
- )
193
-
194
- // 匿名token开关
195
- const ANON_TOKEN_ENABLED = true
196
-
197
- // 从环境变量初始化配置
198
- func initConfig() {
199
- UPSTREAM_URL = getEnv("UPSTREAM_URL", "https://chat.z.ai/api/chat/completions")
200
- DEFAULT_KEY = getEnv("DEFAULT_KEY", "sk-your-key")
201
- ZAI_TOKEN = getEnv("ZAI_TOKEN", "")
202
- MODEL_NAME = getEnv("MODEL_NAME", "GLM-4.5") // 未使用,但保留
203
- PORT = getEnv("PORT", "7860")
204
- MODELS_URL = getEnv("MODELS_URL", "https://chat.z.ai/api/models") // 新增
205
- DEFAULT_UPSTREAM_MODEL_ID = getEnv("DEFAULT_UPSTREAM_MODEL_ID", "0727-360B-API") // 新增
206
- // 处理PORT格式,确保有冒号前缀
207
- if !strings.HasPrefix(PORT, ":") {
208
- PORT = ":" + PORT
209
- }
210
- DEBUG_MODE = getEnv("DEBUG_MODE", "true") == "true"
211
- DEFAULT_STREAM = getEnv("DEFAULT_STREAM", "true") == "true"
212
- DASHBOARD_ENABLED = getEnv("DASHBOARD_ENABLED", "true") == "true"
213
- ENABLE_THINKING = getEnv("ENABLE_THINKING", "true") == "true"
214
  }
215
 
216
  // 记录请求统计信息
217
  func recordRequestStats(startTime time.Time, path string, status int) {
218
  duration := time.Since(startTime)
 
219
  statsMutex.Lock()
220
  defer statsMutex.Unlock()
 
221
  stats.TotalRequests++
222
  stats.LastRequestTime = time.Now()
 
223
  if status >= 200 && status < 300 {
224
  stats.SuccessfulRequests++
225
  } else {
226
  stats.FailedRequests++
227
  }
 
228
  // 更新平均响应时间
229
  if stats.TotalRequests > 0 {
230
  totalDuration := stats.AverageResponseTime*time.Duration(stats.TotalRequests-1) + duration
@@ -238,6 +211,7 @@ func recordRequestStats(startTime time.Time, path string, status int) {
238
  func addLiveRequest(method, path string, status int, duration time.Duration, _, userAgent string) {
239
  requestsMutex.Lock()
240
  defer requestsMutex.Unlock()
 
241
  request := LiveRequest{
242
  ID: fmt.Sprintf("%d", time.Now().UnixNano()),
243
  Timestamp: time.Now(),
@@ -247,46 +221,15 @@ func addLiveRequest(method, path string, status int, duration time.Duration, _,
247
  Duration: duration.Milliseconds(),
248
  UserAgent: userAgent,
249
  }
 
250
  liveRequests = append(liveRequests, request)
 
251
  // 只保留最近的100条请求
252
  if len(liveRequests) > 100 {
253
  liveRequests = liveRequests[1:]
254
  }
255
  }
256
 
257
- // 获取实时请求数据(用于SSE)
258
- func getLiveRequestsData() []byte {
259
- requestsMutex.Lock()
260
- defer requestsMutex.Unlock()
261
- // 确保 liveRequests 不为 nil
262
- if liveRequests == nil {
263
- liveRequests = []LiveRequest{}
264
- }
265
- data, err := json.Marshal(liveRequests)
266
- if err != nil {
267
- // 如果序列化失败,返回空数组
268
- emptyArray := []LiveRequest{}
269
- data, _ = json.Marshal(emptyArray)
270
- }
271
- return data
272
- }
273
-
274
- // 获取统计数据(用于SSE)
275
- func getStatsData() []byte {
276
- statsMutex.Lock()
277
- defer statsMutex.Unlock()
278
- data, _ := json.Marshal(stats)
279
- return data
280
- }
281
-
282
- // 获取环境变量,如果不存在则返回默认值
283
- func getEnv(key, defaultValue string) string {
284
- if value := os.Getenv(key); value != "" {
285
- return value
286
- }
287
- return defaultValue
288
- }
289
-
290
  // 获取客户端IP地址
291
  func getClientIP(r *http.Request) string {
292
  // 检查X-Forwarded-For头
@@ -296,10 +239,12 @@ func getClientIP(r *http.Request) string {
296
  return strings.TrimSpace(ips[0])
297
  }
298
  }
 
299
  // 检查X-Real-IP头
300
  if xri := r.Header.Get("X-Real-IP"); xri != "" {
301
  return xri
302
  }
 
303
  // 使用RemoteAddr
304
  ip := r.RemoteAddr
305
  // 移除端口号
@@ -309,66 +254,82 @@ func getClientIP(r *http.Request) string {
309
  return ip
310
  }
311
 
312
- // 检查字符是否为英文字母
313
- func isEnglishLetter(r rune) bool {
314
- return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z')
315
- }
316
-
317
- // 检查字符串是否包含英文字母
318
- func hasEnglishLetter(s string) bool {
319
- for _, r := range s {
320
- if isEnglishLetter(r) {
321
- return true
322
- }
323
  }
324
- return false
325
  }
326
 
327
- // 检查字符串是否为纯数字
328
- func isDigit(s string) bool {
329
- for _, r := range s {
330
- if r < '0' || r > '9' {
331
- return false
332
- }
 
 
 
333
  }
334
- return s != ""
335
- }
336
 
337
- // 格式化模型名
338
- func formatModelName(name string) string {
339
- if name == "" {
340
- return ""
341
  }
342
- parts := strings.Split(name, "-")
343
- if len(parts) == 1 {
344
- return strings.ToUpper(parts[0])
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  }
346
- formatted := []string{strings.ToUpper(parts[0])}
347
- for _, p := range parts[1:] {
348
- if p == "" {
349
- formatted = append(formatted, "")
350
- } else if isDigit(p) {
351
- formatted = append(formatted, p)
352
- } else if hasEnglishLetter(p) {
353
- // Use Title for better capitalization of letters
354
- formatted = append(formatted, strings.Title(p))
355
- } else {
356
- formatted = append(formatted, p)
357
  }
 
 
 
 
 
358
  }
359
- return strings.Join(formatted, "-")
360
- }
361
 
362
- // 获取模型列表 (新增)
363
- func getModels() []Model {
364
- modelsMutex.RLock()
365
- cachedModels := modelsCache
366
- modelsMutex.RUnlock()
 
 
367
 
368
- if cachedModels != nil {
369
- return cachedModels
 
370
  }
371
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  // 获取token
373
  token := ZAI_TOKEN
374
  if ANON_TOKEN_ENABLED {
@@ -434,15 +395,15 @@ func getModels() []Model {
434
  }
435
 
436
  modelName := m.Name
437
- if modelName == "" || !isEnglishLetter([]rune(modelName)[0]) { // 确保Name不为空且首字符是字母
438
  modelName = formatModelName(m.ID)
439
  }
440
 
441
  models = append(models, Model{
442
  ID: m.ID,
443
  Object: "model",
444
- Name: modelName, // 使用格式化后的名称或原始Name
445
- Created: m.Info.CreatedAt, // 使用上游的CreatedAt
446
  OwnedBy: "z.ai",
447
  })
448
  }
@@ -451,956 +412,212 @@ func getModels() []Model {
451
  return getDefaultModels()
452
  }
453
 
454
- // 更新缓存
455
- modelsMutex.Lock()
456
- modelsCache = models
457
- modelsMutex.Unlock()
458
-
459
  debugLog("获取到%d个模型", len(models))
460
  return models
461
  }
462
 
463
- // 获取默认模型列表(获取失败时使用)
464
- func getDefaultModels() []Model {
465
- return []Model{
466
- {
467
- ID: "0727-360B-API", // 与DEFAULT_UPSTREAM_MODEL_ID一致
468
- Object: "model",
469
- Name: "GLM-4.5", // 或根据ID格式化
470
- Created: time.Now().Unix(),
471
- OwnedBy: "z.ai",
472
- },
473
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  }
475
 
476
- // debug日志函数
477
- func debugLog(format string, args ...interface{}) {
478
- if DEBUG_MODE {
479
- log.Printf("[DEBUG] "+format, args...)
480
- }
481
  }
482
 
483
- // 获取匿名token(每次对话使用不同token,避免共享记忆)
484
- func getAnonymousToken() (string, error) {
485
- client := &http.Client{Timeout: 10 * time.Second}
486
- req, err := http.NewRequest("GET", ORIGIN_BASE+"/api/v1/auths/", nil)
487
- if err != nil {
488
- return "", err
489
- }
490
- // 伪装浏览器头
491
- req.Header.Set("User-Agent", BROWSER_UA)
492
- req.Header.Set("Accept", "*/*")
493
- req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
494
- req.Header.Set("X-FE-Version", X_FE_VERSION)
495
- req.Header.Set("sec-ch-ua", SEC_CH_UA)
496
- req.Header.Set("sec-ch-ua-mobile", SEC_CH_UA_MOB)
497
- req.Header.Set("sec-ch-ua-platform", SEC_CH_UA_PLAT)
498
- req.Header.Set("Origin", ORIGIN_BASE)
499
- req.Header.Set("Referer", ORIGIN_BASE+"/")
500
- resp, err := client.Do(req)
501
- if err != nil {
502
- return "", err
503
- }
504
- defer resp.Body.Close()
505
- if resp.StatusCode != http.StatusOK {
506
- return "", fmt.Errorf("anon token status=%d", resp.StatusCode)
507
- }
508
- var body struct {
509
- Token string `json:"token"`
510
- }
511
- if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
512
- return "", err
513
  }
514
- if body.Token == "" {
515
- return "", fmt.Errorf("anon token empty")
 
 
 
 
 
 
 
516
  }
517
- return body.Token, nil
518
  }
519
 
520
- func main() {
521
- // 初始化配置
522
- initConfig()
523
- // 注册路由
524
- http.HandleFunc("/v1/models", handleModels)
525
- http.HandleFunc("/v1/chat/completions", handleChatCompletions)
526
- http.HandleFunc("/docs", handleAPIDocs)
527
- // http.HandleFunc("/", handleOptions)
528
- http.HandleFunc("/", handleDashboard)
529
- http.HandleFunc("/api/v1/models", handleModels)
530
- http.HandleFunc("/api/v1/chat/completions", handleChatCompletions)
531
- http.HandleFunc("/hf/v1/models", handleModels)
532
- http.HandleFunc("/hf/v1/chat/completions", handleChatCompletions)
533
- // Dashboard路由
534
- if DASHBOARD_ENABLED {
535
- http.HandleFunc("/dashboard", handleDashboard)
536
- http.HandleFunc("/dashboard/stats", handleDashboardStats)
537
- http.HandleFunc("/dashboard/requests", handleDashboardRequests)
538
- log.Printf("Dashboard已启用,访问地址: http://localhost%s/dashboard", PORT)
539
  }
540
- log.Printf("OpenAI兼容API服务器启动在端口%s", PORT)
541
- log.Printf("模型: %s", MODEL_NAME) // 这里可能需要调整显示逻辑,因为现在模型是动态的
542
- log.Printf("上游: %s", UPSTREAM_URL)
543
- log.Printf("Debug模式: %v", DEBUG_MODE)
544
- log.Printf("默认流式响应: %v", DEFAULT_STREAM)
545
- log.Printf("Dashboard启用: %v", DASHBOARD_ENABLED)
546
- log.Printf("思考功能: %v", ENABLE_THINKING)
547
- log.Fatal(http.ListenAndServe(PORT, nil))
548
  }
549
 
550
- // Dashboard页面处理器
551
- func handleDashboard(w http.ResponseWriter, r *http.Request) {
552
- // 只允许GET请求
553
- if r.Method != "GET" {
554
- http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
555
  return
556
  }
557
- // 简单的HTML模板
558
- tmpl := `<!DOCTYPE html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  <html lang="zh-CN">
560
  <head>
561
  <meta charset="UTF-8">
562
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
563
- <title>API调用看板</title>
564
  <style>
565
- body {
566
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
567
- margin: 0;
568
- padding: 20px;
569
- background-color: #f5f5f5;
570
- }
571
- .container {
572
- max-width: 1200px;
573
- margin: 0 auto;
574
- background-color: white;
575
- border-radius: 8px;
576
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
577
- padding: 20px;
578
- }
579
- h1 {
580
- color: #333;
581
- text-align: center;
582
- margin-bottom: 30px;
583
- }
584
- .stats-container {
585
- display: grid;
586
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
587
- gap: 20px;
588
- margin-bottom: 30px;
589
- }
590
- .stat-card {
591
- background-color: #f8f9fa;
592
- border-radius: 6px;
593
- padding: 15px;
594
- text-align: center;
595
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
596
- }
597
- .stat-value {
598
- font-size: 24px;
599
- font-weight: bold;
600
- color: #007bff;
601
- }
602
- .stat-label {
603
- font-size: 14px;
604
- color: #6c757d;
605
- margin-top: 5px;
606
- }
607
- .requests-container {
608
- margin-top: 30px;
609
- }
610
- .requests-table {
611
- width: 100%;
612
- border-collapse: collapse;
613
- }
614
- .requests-table th, .requests-table td {
615
- padding: 10px;
616
- text-align: left;
617
- border-bottom: 1px solid #ddd;
618
- }
619
- .requests-table th {
620
- background-color: #f8f9fa;
621
- }
622
- .status-success {
623
- color: #28a745;
624
- }
625
- .status-error {
626
- color: #dc3545;
627
- }
628
- .refresh-info {
629
- text-align: center;
630
- margin-top: 20px;
631
- color: #007bff;
632
- font-size: 14px;
633
- }
634
- .pagination-container {
635
- display: flex;
636
- justify-content: center;
637
- align-items: center;
638
- margin-top: 20px;
639
- gap: 10px;
640
- }
641
- .pagination-container button {
642
- padding: 5px 10px;
643
- background-color: #007bff;
644
- color: white;
645
- border: none;
646
- border-radius: 4px;
647
- cursor: pointer;
648
- }
649
- .pagination-container button:disabled {
650
- background-color: #cccccc;
651
- cursor: not-allowed;
652
- }
653
- .pagination-container button:hover:not(:disabled) {
654
- background-color: #0056b3;
655
- }
656
- .chart-container {
657
- margin-top: 30px;
658
- height: 300px;
659
- background-color: #f8f9fa;
660
- border-radius: 6px;
661
- padding: 15px;
662
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
663
- }
664
  </style>
 
 
 
 
665
  </head>
666
  <body>
667
  <div class="container">
668
- <h1>API调用看板</h1>
669
- <div class="stats-container">
 
 
 
 
 
670
  <div class="stat-card">
671
- <div class="stat-value" id="total-requests">0</div>
672
  <div class="stat-label">总请求数</div>
673
  </div>
674
  <div class="stat-card">
675
- <div class="stat-value" id="successful-requests">0</div>
676
  <div class="stat-label">成功请求</div>
677
  </div>
678
  <div class="stat-card">
679
- <div class="stat-value" id="failed-requests">0</div>
680
  <div class="stat-label">失败请求</div>
681
  </div>
682
  <div class="stat-card">
683
- <div class="stat-value" id="avg-response-time">0s</div>
684
  <div class="stat-label">平均响应时间</div>
685
  </div>
686
  </div>
687
- <div class="chart-container">
688
- <h2>请求统计图表</h2>
689
- <canvas id="requestsChart"></canvas>
690
- </div>
691
- <div class="requests-container">
692
- <h2>实时请求</h2>
693
- <table class="requests-table">
694
- <thead>
695
- <tr>
696
- <th>时间</th>
697
- <th>模型</th>
698
- <th>方法</th>
699
- <th>状态</th>
700
- <th>耗时</th>
701
- <th>User Agent</th>
702
- </tr>
703
- </thead>
704
- <tbody id="requests-tbody">
705
- <!-- 请求记录将通过JavaScript动态添加 -->
706
- </tbody>
707
- </table>
708
- <div class="pagination-container">
709
- <button id="prev-page" disabled>上一页</button>
710
- <span id="page-info">第 1 页,共 1 页</span>
711
- <button id="next-page" disabled>下一页</button>
712
- </div>
713
- </div>
714
- <div class="refresh-info">
715
- 数据每5秒自动刷新一次
716
- </div>
717
- </div>
718
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
719
- <script>
720
- // 全局变量
721
- let allRequests = [];
722
- let currentPage = 1;
723
- const itemsPerPage = 10;
724
- let requestsChart = null;
725
- // 更新统计数据
726
- function updateStats() {
727
- fetch('/dashboard/stats')
728
- .then(response => response.json())
729
- .then(data => {
730
- document.getElementById('total-requests').textContent = data.TotalRequests;
731
- document.getElementById('successful-requests').textContent = data.SuccessfulRequests;
732
- document.getElementById('failed-requests').textContent = data.FailedRequests;
733
- document.getElementById('avg-response-time').textContent = (data.AverageResponseTime / 1000000000).toFixed(2) + 's';
734
- })
735
- .catch(error => console.error('Error fetching stats:', error));
736
- }
737
- // 更新请求列表
738
- function updateRequests() {
739
- fetch('/dashboard/requests')
740
- .then(response => response.json())
741
- .then(data => {
742
- // 检查数据是否为数组
743
- if (!Array.isArray(data)) {
744
- console.error('返回的数据不是数组:', data);
745
- return;
746
- }
747
- // 保存所有请求数据
748
- allRequests = data;
749
- // 按时间倒序排列
750
- allRequests.sort((a, b) => {
751
- const timeA = new Date(a.timestamp);
752
- const timeB = new Date(b.timestamp);
753
- return timeB - timeA;
754
- });
755
- // 更新表格
756
- updateTable();
757
- // 更新图表
758
- updateChart();
759
- // 更新分页信息
760
- updatePagination();
761
- })
762
- .catch(error => console.error('Error fetching requests:', error));
763
- }
764
- // 更新表格显示
765
- function updateTable() {
766
- const tbody = document.getElementById('requests-tbody');
767
- tbody.innerHTML = '';
768
- // 计算当前页的数据范围
769
- const startIndex = (currentPage - 1) * itemsPerPage;
770
- const endIndex = startIndex + itemsPerPage;
771
- const currentRequests = allRequests.slice(startIndex, endIndex);
772
- currentRequests.forEach(request => {
773
- const row = document.createElement('tr');
774
- // 格式化时间 - 检查时间戳是否有效
775
- let timeStr = "Invalid Date";
776
- if (request.timestamp) {
777
- try {
778
- const time = new Date(request.timestamp);
779
- if (!isNaN(time.getTime())) {
780
- timeStr = time.toLocaleTimeString();
781
- }
782
- } catch (e) {
783
- console.error("时间格式化错误:", e);
784
- }
785
- }
786
- // 状态样式
787
- const statusClass = request.status >= 200 && request.status < 300 ? 'status-success' : 'status-error';
788
- // 截断 User Agent,避免过长
789
- let userAgent = request.user_agent || "undefined";
790
- if (userAgent.length > 30) {
791
- userAgent = userAgent.substring(0, 30) + "...";
792
- }
793
- row.innerHTML = "<td>" + timeStr + "</td>" + "<td>GLM-4.5</td>" + "<td>" + (request.method || "undefined") + "</td>" + "<td class=\"" + statusClass + "\">" + (request.status || "undefined") + "</td>" + "<td>" + ((request.duration / 1000).toFixed(2) || "undefined") + "s</td>" + "<td title=\"" + (request.user_agent || "") + "\">" + userAgent + "</td>";
794
- tbody.appendChild(row);
795
- });
796
- }
797
- // 更新分页信息
798
- function updatePagination() {
799
- const totalPages = Math.ceil(allRequests.length / itemsPerPage);
800
- document.getElementById('page-info').textContent = "第 " + currentPage + " 页,共 " + totalPages + " 页";
801
- document.getElementById('prev-page').disabled = currentPage <= 1;
802
- document.getElementById('next-page').disabled = currentPage >= totalPages;
803
- }
804
- // 更新图表
805
- function updateChart() {
806
- const ctx = document.getElementById('requestsChart').getContext('2d');
807
- // 准备图表数据 - 最近20条请求的响应时间
808
- const chartData = allRequests.slice(0, 20).reverse();
809
- const labels = chartData.map(req => {
810
- const time = new Date(req.timestamp);
811
- return time.toLocaleTimeString();
812
- });
813
- const responseTimes = chartData.map(req => req.duration);
814
- // 如果图表已存在,先销毁
815
- if (requestsChart) {
816
- requestsChart.destroy();
817
- }
818
- // 创建新图表
819
- requestsChart = new Chart(ctx, {
820
- type: 'line',
821
- data: {
822
- labels: labels,
823
- datasets: [{
824
- label: '响应时间 (s)',
825
- data: responseTimes.map(time => time / 1000),
826
- borderColor: '#007bff',
827
- backgroundColor: 'rgba(0, 123, 255, 0.1)',
828
- tension: 0.1,
829
- fill: true
830
- }]
831
- },
832
- options: {
833
- responsive: true,
834
- maintainAspectRatio: false,
835
- scales: {
836
- y: {
837
- beginAtZero: true,
838
- title: {
839
- display: true,
840
- text: '响应时间 (s)'
841
- }
842
- },
843
- x: {
844
- title: {
845
- display: true,
846
- text: '时间'
847
- }
848
- }
849
- },
850
- plugins: {
851
- title: {
852
- display: true,
853
- text: '最近20条请求的响应时间趋势 (s)'
854
- }
855
- }
856
- }
857
- });
858
- }
859
- // 分页按钮事件
860
- document.getElementById('prev-page').addEventListener('click', function() {
861
- if (currentPage > 1) {
862
- currentPage--;
863
- updateTable();
864
- updatePagination();
865
- }
866
- });
867
- document.getElementById('next-page').addEventListener('click', function() {
868
- const totalPages = Math.ceil(allRequests.length / itemsPerPage);
869
- if (currentPage < totalPages) {
870
- currentPage++;
871
- updateTable();
872
- updatePagination();
873
- }
874
- });
875
- // 初始加载
876
- updateStats();
877
- updateRequests();
878
- // 定时刷新
879
- setInterval(updateStats, 5000);
880
- setInterval(updateRequests, 5000);
881
- </script>
882
- </body>
883
- </html>`
884
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
885
- fmt.Fprint(w, tmpl)
886
- }
887
-
888
- // Dashboard统计数据处理器
889
- func handleDashboardStats(w http.ResponseWriter, r *http.Request) {
890
- w.Header().Set("Content-Type", "application/json")
891
- w.Write(getStatsData())
892
- }
893
 
894
- // Dashboard请求数据处理器
895
- func handleDashboardRequests(w http.ResponseWriter, r *http.Request) {
896
- w.Header().Set("Content-Type", "application/json")
897
- w.Write(getLiveRequestsData())
898
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
 
900
- // API文档页面处理器
901
- func handleAPIDocs(w http.ResponseWriter, r *http.Request) {
902
- // 只允许GET请求
903
- if r.Method != "GET" {
904
- http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
905
- return
906
- }
907
- // API文档HTML模板
908
- tmpl := `<!DOCTYPE html>
909
- <html lang="zh-CN">
910
- <head>
911
- <meta charset="UTF-8">
912
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
913
- <title>ZtoApi 文档</title>
914
- <style>
915
- body {
916
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
917
- margin: 0;
918
- padding: 20px;
919
- background-color: #f5f5f5;
920
- line-height: 1.6;
921
- }
922
- .container {
923
- max-width: 1200px;
924
- margin: 0 auto;
925
- background-color: white;
926
- border-radius: 8px;
927
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
928
- padding: 30px;
929
- }
930
- h1 {
931
- color: #333;
932
- text-align: center;
933
- margin-bottom: 30px;
934
- border-bottom: 2px solid #007bff;
935
- padding-bottom: 10px;
936
- }
937
- h2 {
938
- color: #007bff;
939
- margin-top: 30px;
940
- margin-bottom: 15px;
941
- }
942
- h3 {
943
- color: #333;
944
- margin-top: 25px;
945
- margin-bottom: 10px;
946
- }
947
- .endpoint {
948
- background-color: #f8f9fa;
949
- border-radius: 6px;
950
- padding: 15px;
951
- margin-bottom: 20px;
952
- border-left: 4px solid #007bff;
953
- }
954
- .method {
955
- display: inline-block;
956
- padding: 4px 8px;
957
- border-radius: 4px;
958
- color: white;
959
- font-weight: bold;
960
- margin-right: 10px;
961
- font-size: 14px;
962
- }
963
- .get { background-color: #28a745; }
964
- .post { background-color: #007bff; }
965
- .path {
966
- font-family: monospace;
967
- background-color: #e9ecef;
968
- padding: 2px 6px;
969
- border-radius: 3px;
970
- font-size: 16px;
971
- }
972
- .description {
973
- margin: 15px 0;
974
- }
975
- .parameters {
976
- margin: 15px 0;
977
- }
978
- table {
979
- width: 100%;
980
- border-collapse: collapse;
981
- margin: 15px 0;
982
- }
983
- th, td {
984
- padding: 10px;
985
- text-align: left;
986
- border-bottom: 1px solid #ddd;
987
- }
988
- th {
989
- background-color: #f8f9fa;
990
- font-weight: bold;
991
- }
992
- .example {
993
- background-color: #f8f9fa;
994
- border-radius: 6px;
995
- padding: 15px;
996
- margin: 15px 0;
997
- font-family: monospace;
998
- white-space: pre-wrap;
999
- overflow-x: auto;
1000
- }
1001
- .note {
1002
- background-color: #fff3cd;
1003
- border-left: 4px solid #ffc107;
1004
- padding: 10px 15px;
1005
- margin: 15px 0;
1006
- border-radius: 0 4px 4px 0;
1007
- }
1008
- .response {
1009
- background-color: #f8f9fa;
1010
- border-radius: 6px;
1011
- padding: 15px;
1012
- margin: 15px 0;
1013
- font-family: monospace;
1014
- white-space: pre-wrap;
1015
- overflow-x: auto;
1016
- }
1017
- .tab {
1018
- overflow: hidden;
1019
- border: 1px solid #ccc;
1020
- background-color: #f1f1f1;
1021
- border-radius: 4px 4px 0 0;
1022
- }
1023
- .tab button {
1024
- background-color: inherit;
1025
- float: left;
1026
- border: none;
1027
- outline: none;
1028
- cursor: pointer;
1029
- padding: 14px 16px;
1030
- transition: 0.3s;
1031
- font-size: 16px;
1032
- }
1033
- .tab button:hover {
1034
- background-color: #ddd;
1035
- }
1036
- .tab button.active {
1037
- background-color: #ccc;
1038
- }
1039
- .tabcontent {
1040
- display: none;
1041
- padding: 6px 12px;
1042
- border: 1px solid #ccc;
1043
- border-top: none;
1044
- border-radius: 0 0 4px 4px;
1045
- }
1046
- .toc {
1047
- background-color: #f8f9fa;
1048
- border-radius: 6px;
1049
- padding: 15px;
1050
- margin-bottom: 20px;
1051
- }
1052
- .toc ul {
1053
- padding-left: 20px;
1054
- }
1055
- .toc li {
1056
- margin: 5px 0;
1057
- }
1058
- .toc a {
1059
- color: #007bff;
1060
- text-decoration: none;
1061
- }
1062
- .toc a:hover {
1063
- text-decoration: underline;
1064
- }
1065
- </style>
1066
- </head>
1067
- <body>
1068
- <div class="container">
1069
- <h1>ZtoApi 文档</h1>
1070
- <div class="toc">
1071
- <h2>目录</h2>
1072
- <ul>
1073
- <li><a href="#overview">概述</a></li>
1074
- <li><a href="#authentication">身份验证</a></li>
1075
- <li><a href="#endpoints">API端点</a>
1076
- <ul>
1077
- <li><a href="#models">获取模型列表</a></li>
1078
- <li><a href="#chat-completions">聊天完成</a></li>
1079
- </ul>
1080
- </li>
1081
- <li><a href="#examples">使用示例</a></li>
1082
- <li><a href="#error-handling">错误处理</a></li>
1083
- </ul>
1084
  </div>
1085
- <section id="overview">
1086
- <h2>概述</h2>
1087
- <p>这是一个为Z.ai GLM-4.5模型提供OpenAI兼容API接口的代理服务器。它允许你使用标准的OpenAI API格式与Z.ai的GLM-4.5模型进行交互,支持流式和非流式响应。</p>
1088
- <p><strong>基础URL:</strong> <code>http://localhost:7860/v1</code></p>
1089
- <div class="note">
1090
- <strong>注意:</strong> 默认端口为7860,可以通过环境变量PORT进行修改。
1091
- </div>
1092
- </section>
1093
- <section id="authentication">
1094
- <h2>身份验证</h2>
1095
- <p>所有API请求都需要在请求头中包含有效的API密钥进行身份验证:</p>
1096
- <div class="example">
1097
- Authorization: Bearer your-api-key</div>
1098
- <p>默认的API密钥为 <code>sk-your-key</code>,可以通过环境变量 <code>DEFAULT_KEY</code> 进行修改。</p>
1099
- </section>
1100
- <section id="endpoints">
1101
- <h2>API端点</h2>
1102
- <div class="endpoint" id="models">
1103
- <h3>获取模型列表</h3>
1104
- <div>
1105
- <span class="method get">GET</span>
1106
- <span class="path">/v1/models</span>
1107
- </div>
1108
- <div class="description">
1109
- <p>获取可用模型列表。</p>
1110
- </div>
1111
- <div class="parameters">
1112
- <h4>请求参数</h4>
1113
- <p>无</p>
1114
- </div>
1115
- <div class="response">
1116
- {
1117
- "object": "list",
1118
- "data": [
1119
- {
1120
- "id": "GLM-4.5",
1121
- "object": "model",
1122
- "created": 1756788845,
1123
- "owned_by": "z.ai"
1124
- }
1125
- ]
1126
- }</div>
1127
- </div>
1128
- <div class="endpoint" id="chat-completions">
1129
- <h3>聊天完成</h3>
1130
- <div>
1131
- <span class="method post">POST</span>
1132
- <span class="path">/v1/chat/completions</span>
1133
- </div>
1134
- <div class="description">
1135
- <p>基于消息列表生成模型响应。支持流式和非流式两种模式。</p>
1136
- </div>
1137
- <div class="parameters">
1138
- <h4>请求参数</h4>
1139
- <table>
1140
- <thead>
1141
- <tr>
1142
- <th>参数名</th>
1143
- <th>类型</th>
1144
- <th>必需</th>
1145
- <th>说明</th>
1146
- </tr>
1147
- </thead>
1148
- <tbody>
1149
- <tr>
1150
- <td>model</td>
1151
- <td>string</td>
1152
- <td>是</td>
1153
- <td>要使用的模型ID,例如 "GLM-4.5"</td>
1154
- </tr>
1155
- <tr>
1156
- <td>messages</td>
1157
- <td>array</td>
1158
- <td>是</td>
1159
- <td>消息列表,包含角色和内容</td>
1160
- </tr>
1161
- <tr>
1162
- <td>stream</td>
1163
- <td>boolean</td>
1164
- <td>否</td>
1165
- <td>是否使用流式响应,默认为true</td>
1166
- </tr>
1167
- <tr>
1168
- <td>temperature</td>
1169
- <td>number</td>
1170
- <td>否</td>
1171
- <td>采样温度,控制随机性</td>
1172
- </tr>
1173
- <tr>
1174
- <td>max_tokens</td>
1175
- <td>integer</td>
1176
- <td>否</td>
1177
- <td>生成的最大令牌数</td>
1178
- </tr>
1179
- <tr>
1180
- <td>enable_thinking</td>
1181
- <td>boolean</td>
1182
- <td>否</td>
1183
- <td>是否启用思考功能,默认使用环境变量 ENABLE_THINKING 的值</td>
1184
- </tr>
1185
- </tbody>
1186
- </table>
1187
- </div>
1188
- <div class="parameters">
1189
- <h4>消息格式</h4>
1190
- <table>
1191
- <thead>
1192
- <tr>
1193
- <th>字段</th>
1194
- <th>类型</th>
1195
- <th>说明</th>
1196
- </tr>
1197
- </thead>
1198
- <tbody>
1199
- <tr>
1200
- <td>role</td>
1201
- <td>string</td>
1202
- <td>消息角色,可选值:system、user、assistant</td>
1203
- </tr>
1204
- <tr>
1205
- <td>content</td>
1206
- <td>string</td>
1207
- <td>消息内容</td>
1208
- </tr>
1209
- </tbody>
1210
- </table>
1211
- </div>
1212
- </div>
1213
- </section>
1214
- <section id="examples">
1215
- <h2>使用示例</h2>
1216
- <div class="tab">
1217
- <button class="tablinks active" onclick="openTab(event, 'python-tab')">Python</button>
1218
- <button class="tablinks" onclick="openTab(event, 'curl-tab')">cURL</button>
1219
- <button class="tablinks" onclick="openTab(event, 'javascript-tab')">JavaScript</button>
1220
- </div>
1221
- <div id="python-tab" class="tabcontent" style="display: block;">
1222
- <h3>Python示例</h3>
1223
- <div class="example">
1224
- import openai
1225
- # 配置客户端
1226
- client = openai.OpenAI(
1227
- api_key="your-api-key", # 对应 DEFAULT_KEY
1228
- base_url="http://localhost:7860/v1"
1229
- )
1230
- # 非流式请求
1231
- response = client.chat.completions.create(
1232
- model="GLM-4.5",
1233
- messages=[{"role": "user", "content": "你好,请介绍一下自己"}]
1234
- )
1235
- print(response.choices[0].message.content)
1236
- # 流式请求
1237
- response = client.chat.completions.create(
1238
- model="GLM-4.5",
1239
- messages=[{"role": "user", "content": "请写一首关于春天的诗"}],
1240
- stream=True
1241
- )
1242
- for chunk in response:
1243
- if chunk.choices[0].delta.content:
1244
- print(chunk.choices[0].delta.content, end="")</div>
1245
- </div>
1246
- <div id="curl-tab" class="tabcontent">
1247
- <h3>cURL示例</h3>
1248
- <div class="example">
1249
- # 非流式请求
1250
- curl -X POST http://localhost:7860/v1/chat/completions \
1251
- -H "Content-Type: application/json" \
1252
- -H "Authorization: Bearer your-api-key" \
1253
- -d '{
1254
- "model": "GLM-4.5",
1255
- "messages": [{"role": "user", "content": "你好"}],
1256
- "stream": false
1257
- }'
1258
- # 流式请求
1259
- curl -X POST http://localhost:7860/v1/chat/completions \
1260
- -H "Content-Type: application/json" \
1261
- -H "Authorization: Bearer your-api-key" \
1262
- -d '{
1263
- "model": "GLM-4.5",
1264
- "messages": [{"role": "user", "content": "你好"}],
1265
- "stream": true
1266
- }'</div>
1267
- # 启用思考功能的请求
1268
- curl -X POST http://localhost:7860/v1/chat/completions \
1269
- -H "Content-Type: application/json" \
1270
- -H "Authorization: Bearer your-api-key" \
1271
- -d '{
1272
- "model": "GLM-4.5",
1273
- "messages": [{"role": "user", "content": "请分析一下这个问题"}],
1274
- "enable_thinking": true
1275
- }'
1276
- </div>
1277
- <div id="javascript-tab" class="tabcontent">
1278
- <h3>JavaScript示例</h3>
1279
- <div class="example">
1280
- const fetch = require('node-fetch');
1281
- async function chatWithGLM(message, stream = false) {
1282
- const response = await fetch('http://localhost:7860/v1/chat/completions', {
1283
- method: 'POST',
1284
- headers: {
1285
- 'Content-Type': 'application/json',
1286
- 'Authorization': 'Bearer your-api-key'
1287
- },
1288
- body: JSON.stringify({
1289
- model: 'GLM-4.5',
1290
- messages: [{ role: 'user', content: message }],
1291
- stream: stream
1292
- })
1293
- });
1294
- if (stream) {
1295
- // 处理流式响应
1296
- const reader = response.body.getReader();
1297
- const decoder = new TextDecoder();
1298
- while (true) {
1299
- const { done, value } = await reader.read();
1300
- if (done) break;
1301
- const chunk = decoder.decode(value);
1302
- const lines = chunk.split('\n');
1303
- for (const line of lines) {
1304
- if (line.startsWith('data: ')) {
1305
- const data = line.slice(6);
1306
- if (data === '[DONE]') {
1307
- console.log('\n流式响应完成');
1308
- return;
1309
- }
1310
- try {
1311
- const parsed = JSON.parse(data);
1312
- const content = parsed.choices[0]?.delta?.content;
1313
- if (content) {
1314
- process.stdout.write(content);
1315
- }
1316
- } catch (e) {
1317
- // 忽略解析错误
1318
- }
1319
- }
1320
- }
1321
- }
1322
- } else {
1323
- // 处理非流式响应
1324
- const data = await response.json();
1325
- console.log(data.choices[0].message.content);
1326
- }
1327
- }
1328
- // 使用示例
1329
- chatWithGLM('你好,请介绍一下JavaScript', false);</div>
1330
- </div>
1331
- </section>
1332
- <section id="error-handling">
1333
- <h2>错误处理</h2>
1334
- <p>API使用标准HTTP状态码来表示请求的成功或失败:</p>
1335
- <table>
1336
- <thead>
1337
- <tr>
1338
- <th>状态码</th>
1339
- <th>说明</th>
1340
- </tr>
1341
- </thead>
1342
- <tbody>
1343
- <tr>
1344
- <td>200 OK</td>
1345
- <td>请求成功</td>
1346
- </tr>
1347
- <tr>
1348
- <td>400 Bad Request</td>
1349
- <td>请求格式错误或参数无效</td>
1350
- </tr>
1351
- <tr>
1352
- <td>401 Unauthorized</td>
1353
- <td>API密钥无效或缺失</td>
1354
- </tr>
1355
- <tr>
1356
- <td>502 Bad Gateway</td>
1357
- <td>上游服务错误</td>
1358
- </tr>
1359
- </tbody>
1360
- </table>
1361
- <div class="note">
1362
- <strong>注意:</strong> 在调试模式下,���务器会输出详细的日志信息,可以通过设置环境变量 DEBUG_MODE=true 来启用。
1363
- </div>
1364
- </section>
1365
  </div>
1366
- <script>
1367
- function openTab(evt, tabName) {
1368
- var i, tabcontent, tablinks;
1369
- tabcontent = document.getElementsByClassName("tabcontent");
1370
- for (i = 0; i < tabcontent.length; i++) {
1371
- tabcontent[i].style.display = "none";
1372
- }
1373
- tablinks = document.getElementsByClassName("tablinks");
1374
- for (i = 0; i < tablinks.length; i++) {
1375
- tablinks[i].className = tablinks[i].className.replace(" active", "");
1376
- }
1377
- document.getElementById(tabName).style.display = "block";
1378
- evt.currentTarget.className += " active";
1379
- }
1380
- </script>
1381
  </body>
1382
  </html>`
1383
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
1384
- fmt.Fprint(w, tmpl)
1385
- }
1386
 
1387
- func handleOptions(w http.ResponseWriter, r *http.Request) {
1388
- setCORSHeaders(w)
1389
- if r.Method == "OPTIONS" {
1390
- w.WriteHeader(http.StatusOK)
1391
- return
1392
- }
1393
- w.WriteHeader(http.StatusNotFound)
1394
- }
1395
-
1396
- func setCORSHeaders(w http.ResponseWriter) {
1397
- w.Header().Set("Access-Control-Allow-Origin", "*")
1398
- w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
1399
- w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
1400
- w.Header().Set("Access-Control-Allow-Credentials", "true")
1401
  }
1402
 
1403
- // 修改 handleModels 函数,调用 getModels
1404
  func handleModels(w http.ResponseWriter, r *http.Request) {
1405
  setCORSHeaders(w)
1406
  if r.Method == "OPTIONS" {
@@ -1410,48 +627,64 @@ func handleModels(w http.ResponseWriter, r *http.Request) {
1410
 
1411
  response := ModelsResponse{
1412
  Object: "list",
1413
- Data: getModels(), // 调用 getModels 获取列表
1414
  }
1415
 
1416
  w.Header().Set("Content-Type", "application/json")
1417
  json.NewEncoder(w).Encode(response)
1418
  }
1419
 
 
 
 
 
 
 
 
 
 
1420
  func handleChatCompletions(w http.ResponseWriter, r *http.Request) {
1421
  startTime := time.Now()
1422
  path := r.URL.Path
1423
  clientIP := getClientIP(r)
1424
  userAgent := r.UserAgent()
 
1425
  setCORSHeaders(w)
1426
  if r.Method == "OPTIONS" {
1427
  w.WriteHeader(http.StatusOK)
1428
  return
1429
  }
 
1430
  debugLog("收到chat completions请求")
1431
 
1432
- // 验证API Key
1433
- authHeader := r.Header.Get("Authorization")
1434
- if !strings.HasPrefix(authHeader, "Bearer ") {
1435
- debugLog("缺少或无效的Authorization头")
1436
- http.Error(w, "Missing or invalid Authorization header", http.StatusUnauthorized)
1437
- // 记录请求统计
1438
- duration := time.Since(startTime)
1439
- recordRequestStats(startTime, path, http.StatusUnauthorized)
1440
- addLiveRequest(r.Method, path, http.StatusUnauthorized, duration, "", userAgent)
1441
- return
1442
- }
1443
- //apiKey := strings.TrimPrefix(authHeader, "Bearer ")
1444
- //if apiKey != DEFAULT_KEY {
1445
- // debugLog("无效的API key: %s", apiKey)
1446
- // http.Error(w, "Invalid API key", http.StatusUnauthorized)
1447
- // // 记录请求统计
1448
- // duration := time.Since(startTime)
1449
- // recordRequestStats(startTime, path, http.StatusUnauthorized)
1450
- // addLiveRequest(r.Method, path, http.StatusUnauthorized, duration, "", userAgent)
1451
- // return
1452
- //}
1453
- //
1454
- //debugLog("API key验证通过")
 
 
 
 
 
1455
  // 读取请求体
1456
  body, err := io.ReadAll(r.Body)
1457
  if err != nil {
@@ -1463,6 +696,7 @@ func handleChatCompletions(w http.ResponseWriter, r *http.Request) {
1463
  addLiveRequest(r.Method, path, http.StatusBadRequest, duration, "", userAgent)
1464
  return
1465
  }
 
1466
  // 解析请��
1467
  var req OpenAIRequest
1468
  if err := json.Unmarshal(body, &req); err != nil {
@@ -1475,52 +709,28 @@ func handleChatCompletions(w http.ResponseWriter, r *http.Request) {
1475
  return
1476
  }
1477
 
1478
- // --- 模型映射逻辑 ---
1479
- models := getModels()
1480
- modelExists := false
1481
- for _, m := range models {
1482
- if m.ID == req.Model {
1483
- modelExists = true
1484
- break
1485
- }
1486
- }
1487
- actualUpstreamModelID := req.Model // 默认使用请求的模型ID
1488
- if !modelExists {
1489
- debugLog("未知模型 '%s',映射到默认上游模型 '%s'", req.Model, DEFAULT_UPSTREAM_MODEL_ID)
1490
- actualUpstreamModelID = DEFAULT_UPSTREAM_MODEL_ID // 映射到默认模型
1491
- }
1492
- // --- 模型映射逻辑结束 ---
1493
-
1494
  // 如果客户端没有明确指定stream参数,使用默认值
1495
  if !bytes.Contains(body, []byte(`"stream"`)) {
1496
  req.Stream = DEFAULT_STREAM
1497
  debugLog("客户端未指定stream参数,使用默认值: %v", DEFAULT_STREAM)
1498
  }
1499
- debugLog("请求解析成功 - 模型: %s (映射后: %s), 流式: %v, 消息数: %d", req.Model, actualUpstreamModelID, req.Stream, len(req.Messages))
 
1500
 
1501
  // 生成会话相关ID
1502
  chatID := fmt.Sprintf("%d-%d", time.Now().UnixNano(), time.Now().Unix())
1503
  msgID := fmt.Sprintf("%d", time.Now().UnixNano())
1504
 
1505
- // 决定是否启用思考功能:优先使用请求参数,其次使用环境变量
1506
- enableThinking := ENABLE_THINKING // 默认使用环境变量值
1507
- if req.EnableThinking != nil {
1508
- enableThinking = *req.EnableThinking
1509
- debugLog("使用请求参数中的思考功能设置: %v", enableThinking)
1510
- } else {
1511
- debugLog("使用环境变量中的思考功能设置: %v", enableThinking)
1512
- }
1513
-
1514
- // 构造上游请求 - 使用映射后的模型ID
1515
  upstreamReq := UpstreamRequest{
1516
  Stream: true, // 总是使用流式从上游获取
1517
  ChatID: chatID,
1518
  ID: msgID,
1519
- Model: actualUpstreamModelID, // 使用映射后的ID
1520
  Messages: req.Messages,
1521
  Params: map[string]interface{}{},
1522
  Features: map[string]interface{}{
1523
- "enable_thinking": enableThinking,
1524
  },
1525
  BackgroundTasks: map[string]bool{
1526
  "title_generation": false,
@@ -1531,7 +741,7 @@ func handleChatCompletions(w http.ResponseWriter, r *http.Request) {
1531
  ID string `json:"id"`
1532
  Name string `json:"name"`
1533
  OwnedBy string `json:"owned_by"`
1534
- }{ID: actualUpstreamModelID, Name: req.Model, OwnedBy: "openai"}, // ModelItem.ID也用映射后的,Name可以保留原始请求的ID或按需设置
1535
  ToolServers: []string{},
1536
  Variables: map[string]string{
1537
  "{{USER_NAME}}": "User",
@@ -1556,27 +766,31 @@ func handleChatCompletions(w http.ResponseWriter, r *http.Request) {
1556
  }
1557
  }
1558
 
1559
- // 调用上游API,传入原始请求的模型ID用于响应
1560
  if req.Stream {
1561
- handleStreamResponseWithIDs(w, upstreamReq, chatID, authToken, startTime, path, clientIP, userAgent, req.Model) // 传入原始模型ID
1562
  } else {
1563
- handleNonStreamResponseWithIDs(w, upstreamReq, chatID, authToken, startTime, path, clientIP, userAgent, req.Model) // 传入原始模型ID
1564
  }
1565
  }
1566
 
 
1567
  func callUpstreamWithHeaders(upstreamReq UpstreamRequest, refererChatID string, authToken string) (*http.Response, error) {
1568
  reqBody, err := json.Marshal(upstreamReq)
1569
  if err != nil {
1570
  debugLog("上游请求序列化失败: %v", err)
1571
  return nil, err
1572
  }
 
1573
  debugLog("调用上游API: %s", UPSTREAM_URL)
1574
  debugLog("上游请求体: %s", string(reqBody))
 
1575
  req, err := http.NewRequest("POST", UPSTREAM_URL, bytes.NewBuffer(reqBody))
1576
  if err != nil {
1577
  debugLog("创建HTTP请求失败: %v", err)
1578
  return nil, err
1579
  }
 
1580
  req.Header.Set("Content-Type", "application/json")
1581
  req.Header.Set("Accept", "application/json, text/event-stream")
1582
  req.Header.Set("User-Agent", BROWSER_UA)
@@ -1588,19 +802,22 @@ func callUpstreamWithHeaders(upstreamReq UpstreamRequest, refererChatID string,
1588
  req.Header.Set("X-FE-Version", X_FE_VERSION)
1589
  req.Header.Set("Origin", ORIGIN_BASE)
1590
  req.Header.Set("Referer", ORIGIN_BASE+"/c/"+refererChatID)
 
1591
  client := &http.Client{Timeout: 60 * time.Second}
1592
  resp, err := client.Do(req)
1593
  if err != nil {
1594
  debugLog("上游请求失败: %v", err)
1595
  return nil, err
1596
  }
 
1597
  debugLog("上游响应状态: %d %s", resp.StatusCode, resp.Status)
1598
  return resp, nil
1599
  }
1600
 
1601
- // 修改 handleStreamResponseWithIDs 函数,增加原始模型ID参数
1602
- func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequest, chatID string, authToken string, startTime time.Time, path string, clientIP, userAgent string, originalModelID string) { // 增加 originalModelID 参数
1603
- debugLog("开始��理流式响应 (chat_id=%s, original_model=%s)", chatID, originalModelID)
 
1604
  resp, err := callUpstreamWithHeaders(upstreamReq, chatID, authToken)
1605
  if err != nil {
1606
  debugLog("调用上游失败: %v", err)
@@ -1612,6 +829,7 @@ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequ
1612
  return
1613
  }
1614
  defer resp.Body.Close()
 
1615
  if resp.StatusCode != http.StatusOK {
1616
  debugLog("上游返回错误状态: %d", resp.StatusCode)
1617
  // 读取错误响应体
@@ -1646,7 +864,7 @@ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequ
1646
  }
1647
  // 处理每行前缀 "> "(包括起始位置)
1648
  s = strings.TrimPrefix(s, "> ")
1649
- s = strings.ReplaceAll(s, "\n> ", "\n") // <--- 修正换行符
1650
  return strings.TrimSpace(s)
1651
  }
1652
 
@@ -1654,18 +872,19 @@ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequ
1654
  w.Header().Set("Content-Type", "text/event-stream")
1655
  w.Header().Set("Cache-Control", "no-cache")
1656
  w.Header().Set("Connection", "keep-alive")
 
1657
  flusher, ok := w.(http.Flusher)
1658
  if !ok {
1659
  http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
1660
  return
1661
  }
1662
 
1663
- // 发送第一个chunk(role),使用原始模型ID
1664
  firstChunk := OpenAIResponse{
1665
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
1666
  Object: "chat.completion.chunk",
1667
  Created: time.Now().Unix(),
1668
- Model: originalModelID, // 使用原始模型ID
1669
  Choices: []Choice{
1670
  {
1671
  Index: 0,
@@ -1680,17 +899,22 @@ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequ
1680
  debugLog("开始读取上游SSE流")
1681
  scanner := bufio.NewScanner(resp.Body)
1682
  lineCount := 0
 
1683
  for scanner.Scan() {
1684
  line := scanner.Text()
1685
  lineCount++
 
1686
  if !strings.HasPrefix(line, "data: ") {
1687
  continue
1688
  }
 
1689
  dataStr := strings.TrimPrefix(line, "data: ")
1690
  if dataStr == "" {
1691
  continue
1692
  }
 
1693
  debugLog("收到SSE数据 (第%d行): %s", lineCount, dataStr)
 
1694
  var upstreamData UpstreamData
1695
  if err := json.Unmarshal([]byte(dataStr), &upstreamData); err != nil {
1696
  debugLog("SSE数据解析失败: %v", err)
@@ -1712,11 +936,11 @@ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequ
1712
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
1713
  Object: "chat.completion.chunk",
1714
  Created: time.Now().Unix(),
1715
- Model: originalModelID, // 使用原始模型ID
1716
  Choices: []Choice{{Index: 0, Delta: Delta{}, FinishReason: "stop"}},
1717
  }
1718
  writeSSEChunk(w, endChunk)
1719
- fmt.Fprintf(w, "data: [DONE]\n")
1720
  flusher.Flush()
1721
  break
1722
  }
@@ -1736,7 +960,7 @@ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequ
1736
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
1737
  Object: "chat.completion.chunk",
1738
  Created: time.Now().Unix(),
1739
- Model: originalModelID, // 使用原始模型ID
1740
  Choices: []Choice{
1741
  {
1742
  Index: 0,
@@ -1757,7 +981,7 @@ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequ
1757
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
1758
  Object: "chat.completion.chunk",
1759
  Created: time.Now().Unix(),
1760
- Model: originalModelID, // 使用原始模型ID
1761
  Choices: []Choice{
1762
  {
1763
  Index: 0,
@@ -1768,13 +992,15 @@ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequ
1768
  }
1769
  writeSSEChunk(w, endChunk)
1770
  flusher.Flush()
 
1771
  // 发送[DONE]
1772
- fmt.Fprintf(w, "data: [DONE]\n")
1773
  flusher.Flush()
1774
  debugLog("流式响应完成,共处理%d行", lineCount)
1775
  break
1776
  }
1777
  }
 
1778
  if err := scanner.Err(); err != nil {
1779
  debugLog("扫描器错误: %v", err)
1780
  }
@@ -1785,14 +1011,16 @@ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequ
1785
  addLiveRequest("POST", path, http.StatusOK, duration, "", userAgent)
1786
  }
1787
 
 
1788
  func writeSSEChunk(w http.ResponseWriter, chunk OpenAIResponse) {
1789
  data, _ := json.Marshal(chunk)
1790
- fmt.Fprintf(w, "data: %s\n", data)
1791
  }
1792
 
1793
- // 修改 handleNonStreamResponseWithIDs 函数,增加原始模型ID参数
1794
- func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequest, chatID string, authToken string, startTime time.Time, path string, clientIP, userAgent string, originalModelID string) { // 增加 originalModelID 参数
1795
- debugLog("开始处理非流式响应 (chat_id=%s, original_model=%s)", chatID, originalModelID)
 
1796
  resp, err := callUpstreamWithHeaders(upstreamReq, chatID, authToken)
1797
  if err != nil {
1798
  debugLog("调用上游失败: %v", err)
@@ -1804,6 +1032,7 @@ func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamR
1804
  return
1805
  }
1806
  defer resp.Body.Close()
 
1807
  if resp.StatusCode != http.StatusOK {
1808
  debugLog("上游返回错误状态: %d", resp.StatusCode)
1809
  // 读取错误响应体
@@ -1823,19 +1052,23 @@ func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamR
1823
  var fullContent strings.Builder
1824
  scanner := bufio.NewScanner(resp.Body)
1825
  debugLog("开始收集完整响应内容")
 
1826
  for scanner.Scan() {
1827
  line := scanner.Text()
1828
  if !strings.HasPrefix(line, "data: ") {
1829
  continue
1830
  }
 
1831
  dataStr := strings.TrimPrefix(line, "data: ")
1832
  if dataStr == "" {
1833
  continue
1834
  }
 
1835
  var upstreamData UpstreamData
1836
  if err := json.Unmarshal([]byte(dataStr), &upstreamData); err != nil {
1837
  continue
1838
  }
 
1839
  if upstreamData.Data.DeltaContent != "" {
1840
  out := upstreamData.Data.DeltaContent
1841
  if upstreamData.Data.Phase == "thinking" {
@@ -1855,7 +1088,7 @@ func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamR
1855
  s = strings.ReplaceAll(s, "</details>", "")
1856
  }
1857
  s = strings.TrimPrefix(s, "> ")
1858
- s = strings.ReplaceAll(s, "\n> ", "\n") // <--- 修正换行符
1859
  return strings.TrimSpace(s)
1860
  }(out)
1861
  }
@@ -1863,20 +1096,22 @@ func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamR
1863
  fullContent.WriteString(out)
1864
  }
1865
  }
 
1866
  if upstreamData.Data.Done || upstreamData.Data.Phase == "done" {
1867
  debugLog("检测到完成信号,停止收集")
1868
  break
1869
  }
1870
  }
 
1871
  finalContent := fullContent.String()
1872
  debugLog("内容收集完成,最终长度: %d", len(finalContent))
1873
 
1874
- // 构造完整响应,使用原始模型ID
1875
  response := OpenAIResponse{
1876
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
1877
  Object: "chat.completion",
1878
  Created: time.Now().Unix(),
1879
- Model: originalModelID, // 使用原始模型ID
1880
  Choices: []Choice{
1881
  {
1882
  Index: 0,
@@ -1893,6 +1128,7 @@ func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamR
1893
  TotalTokens: 0,
1894
  },
1895
  }
 
1896
  w.Header().Set("Content-Type", "application/json")
1897
  json.NewEncoder(w).Encode(response)
1898
  debugLog("非流式响应发送完成")
@@ -1902,3 +1138,37 @@ func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamR
1902
  recordRequestStats(startTime, path, http.StatusOK)
1903
  addLiveRequest("POST", path, http.StatusOK, duration, "", userAgent)
1904
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  "io"
9
  "log"
10
  "net/http"
 
11
  "regexp"
12
  "strings"
13
  "sync"
14
  "time"
15
  )
16
 
17
+ // 配置变量(硬编码,不再从环境变量读取)
18
  var (
19
+ UPSTREAM_URL = "https://chat.z.ai/api/chat/completions"
20
+ MODELS_URL = "https://chat.z.ai/api/models"
21
+ DEFAULT_KEY = "sk-your-key"
22
+ ZAI_TOKEN = ""
23
+ MODEL_NAME = "GLM-4.5"
24
+ PORT = ":7860"
25
+ DEBUG_MODE = true
26
+ DEFAULT_STREAM = true
27
+ DASHBOARD_ENABLED = true
 
 
28
  )
29
 
30
  // 请求统计信息
 
47
  UserAgent string `json:"user_agent"`
48
  }
49
 
50
+ // 全局变量
51
+ var (
52
+ stats RequestStats
53
+ liveRequests = []LiveRequest{}
54
+ statsMutex sync.Mutex
55
+ requestsMutex sync.Mutex
56
+ startTime = time.Now() // 服务启动时间
57
+ )
58
 
59
+ // 思考内容处理策略
60
+ const (
61
+ THINK_TAGS_MODE = "strip" // strip: 去除<details>标签;think: 转为<think>标签;raw: 保留原样
62
+ )
63
+
64
+ // 伪装前端头部(来自抓包)
65
+ const (
66
+ X_FE_VERSION = "prod-fe-1.0.76"
67
+ BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
68
+ SEC_CH_UA = "\"Not;A=Brand\";v=\"99\", \"Edge\";v=\"139\""
69
+ SEC_CH_UA_MOB = "?0"
70
+ SEC_CH_UA_PLAT = "\"Windows\""
71
+ ORIGIN_BASE = "https://chat.z.ai"
72
+ )
73
+
74
+ // 匿名token开关
75
+ const ANON_TOKEN_ENABLED = true
76
 
77
  // OpenAI 请求结构
78
  type OpenAIRequest struct {
79
+ Model string `json:"model"`
80
+ Messages []Message `json:"messages"`
81
+ Stream bool `json:"stream,omitempty"`
82
+ Temperature float64 `json:"temperature,omitempty"`
83
+ MaxTokens int `json:"max_tokens,omitempty"`
 
84
  }
85
 
86
  type Message struct {
 
138
 
139
  // 上游SSE响应结构
140
  type UpstreamData struct {
141
+ Type string `json:"type"`
142
+ Error *UpstreamError `json:"error,omitempty"`
143
+ Data struct {
144
  Phase string `json:"phase"`
145
+ DeltaContent string `json:"delta_content"`
146
  Done bool `json:"done"`
 
147
  Error *UpstreamError `json:"error,omitempty"`
148
  Inner *struct {
149
  Error *UpstreamError `json:"error,omitempty"`
150
+ } `json:"inner,omitempty"`
151
  } `json:"data"`
 
152
  }
153
 
154
  type UpstreamError struct {
 
163
  }
164
 
165
  type Model struct {
166
+ ID string `json:"id"`
167
  Object string `json:"object"`
168
+ Name string `json:"name,omitempty"`
169
  Created int64 `json:"created"`
170
  OwnedBy string `json:"owned_by"`
171
  }
172
 
173
+ // 上游模型响应结构
174
+ type UpstreamModelsResponse struct {
175
+ Data []struct {
176
+ ID string `json:"id"`
177
+ Name string `json:"name"`
178
+ Info struct {
179
+ IsActive bool `json:"is_active"`
180
+ CreatedAt int64 `json:"created_at"`
181
+ } `json:"info"`
182
+ } `json:"data"`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  }
184
 
185
  // 记录请求统计信息
186
  func recordRequestStats(startTime time.Time, path string, status int) {
187
  duration := time.Since(startTime)
188
+
189
  statsMutex.Lock()
190
  defer statsMutex.Unlock()
191
+
192
  stats.TotalRequests++
193
  stats.LastRequestTime = time.Now()
194
+
195
  if status >= 200 && status < 300 {
196
  stats.SuccessfulRequests++
197
  } else {
198
  stats.FailedRequests++
199
  }
200
+
201
  // 更新平均响应时间
202
  if stats.TotalRequests > 0 {
203
  totalDuration := stats.AverageResponseTime*time.Duration(stats.TotalRequests-1) + duration
 
211
  func addLiveRequest(method, path string, status int, duration time.Duration, _, userAgent string) {
212
  requestsMutex.Lock()
213
  defer requestsMutex.Unlock()
214
+
215
  request := LiveRequest{
216
  ID: fmt.Sprintf("%d", time.Now().UnixNano()),
217
  Timestamp: time.Now(),
 
221
  Duration: duration.Milliseconds(),
222
  UserAgent: userAgent,
223
  }
224
+
225
  liveRequests = append(liveRequests, request)
226
+
227
  // 只保留最近的100条请求
228
  if len(liveRequests) > 100 {
229
  liveRequests = liveRequests[1:]
230
  }
231
  }
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  // 获取客户端IP地址
234
  func getClientIP(r *http.Request) string {
235
  // 检查X-Forwarded-For头
 
239
  return strings.TrimSpace(ips[0])
240
  }
241
  }
242
+
243
  // 检查X-Real-IP头
244
  if xri := r.Header.Get("X-Real-IP"); xri != "" {
245
  return xri
246
  }
247
+
248
  // 使用RemoteAddr
249
  ip := r.RemoteAddr
250
  // 移除端口号
 
254
  return ip
255
  }
256
 
257
+ // 调试日志
258
+ func debugLog(format string, args ...interface{}) {
259
+ if DEBUG_MODE {
260
+ log.Printf("[DEBUG] "+format, args...)
 
 
 
 
 
 
 
261
  }
 
262
  }
263
 
264
+ // 获取匿名token(每次对话使用不同token,避免共享记忆)
265
+ func getAnonymousToken() (string, error) {
266
+ client := &http.Client{
267
+ Timeout: 10 * time.Second,
268
+ Transport: &http.Transport{
269
+ MaxIdleConns: 10,
270
+ IdleConnTimeout: 30 * time.Second,
271
+ DisableCompression: false,
272
+ },
273
  }
 
 
274
 
275
+ req, err := http.NewRequest("GET", ORIGIN_BASE+"/api/v1/auths/", nil)
276
+ if err != nil {
277
+ debugLog("创建匿名token请求失败: %v", err)
278
+ return "", fmt.Errorf("创建请求失败: %w", err)
279
  }
280
+
281
+ // 伪装浏览器头
282
+ req.Header.Set("User-Agent", BROWSER_UA)
283
+ req.Header.Set("Accept", "*/*")
284
+ req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
285
+ req.Header.Set("X-FE-Version", X_FE_VERSION)
286
+ req.Header.Set("sec-ch-ua", SEC_CH_UA)
287
+ req.Header.Set("sec-ch-ua-mobile", SEC_CH_UA_MOB)
288
+ req.Header.Set("sec-ch-ua-platform", SEC_CH_UA_PLAT)
289
+ req.Header.Set("Origin", ORIGIN_BASE)
290
+ req.Header.Set("Referer", ORIGIN_BASE+"/")
291
+
292
+ resp, err := client.Do(req)
293
+ if err != nil {
294
+ debugLog("匿名token请求失败: %v", err)
295
+ return "", fmt.Errorf("请求失败: %w", err)
296
  }
297
+ defer func() {
298
+ if closeErr := resp.Body.Close(); closeErr != nil {
299
+ debugLog("关闭响应体失败: %v", closeErr)
 
 
 
 
 
 
 
 
300
  }
301
+ }()
302
+
303
+ if resp.StatusCode != http.StatusOK {
304
+ debugLog("匿名token响应状态码异常: %d", resp.StatusCode)
305
+ return "", fmt.Errorf("服务器响应错误,状态码: %d", resp.StatusCode)
306
  }
 
 
307
 
308
+ var body struct {
309
+ Token string `json:"token"`
310
+ }
311
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
312
+ debugLog("匿名token响应解析失败: %v", err)
313
+ return "", fmt.Errorf("响应解析失败: %w", err)
314
+ }
315
 
316
+ if body.Token == "" {
317
+ debugLog("匿名token为空")
318
+ return "", fmt.Errorf("获取到的token为空")
319
  }
320
 
321
+ debugLog("匿名token获取成功: %s...", func() string {
322
+ if len(body.Token) > 10 {
323
+ return body.Token[:10]
324
+ }
325
+ return body.Token
326
+ }())
327
+
328
+ return body.Token, nil
329
+ }
330
+
331
+ // 获取模型列表
332
+ func getModels() []Model {
333
  // 获取token
334
  token := ZAI_TOKEN
335
  if ANON_TOKEN_ENABLED {
 
395
  }
396
 
397
  modelName := m.Name
398
+ if modelName == "" || !isEnglishLetter(modelName[0]) {
399
  modelName = formatModelName(m.ID)
400
  }
401
 
402
  models = append(models, Model{
403
  ID: m.ID,
404
  Object: "model",
405
+ Name: modelName,
406
+ Created: m.Info.CreatedAt,
407
  OwnedBy: "z.ai",
408
  })
409
  }
 
412
  return getDefaultModels()
413
  }
414
 
 
 
 
 
 
415
  debugLog("获取到%d个模型", len(models))
416
  return models
417
  }
418
 
419
+ // 格式化模型名
420
+ func formatModelName(name string) string {
421
+ if name == "" {
422
+ return ""
423
+ }
424
+ parts := strings.Split(name, "-")
425
+ if len(parts) == 1 {
426
+ return strings.ToUpper(parts[0])
 
 
427
  }
428
+ formatted := []string{strings.ToUpper(parts[0])}
429
+ for _, p := range parts[1:] {
430
+ if p == "" {
431
+ formatted = append(formatted, "")
432
+ } else if isDigit(p) {
433
+ formatted = append(formatted, p)
434
+ } else if hasEnglishLetter(p) {
435
+ formatted = append(formatted, strings.Title(p))
436
+ } else {
437
+ formatted = append(formatted, p)
438
+ }
439
+ }
440
+ return strings.Join(formatted, "-")
441
  }
442
 
443
+ // 判断是否是英文字符
444
+ func isEnglishLetter(ch byte) bool {
445
+ return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
 
 
446
  }
447
 
448
+ // 判断字符串是否全为数字
449
+ func isDigit(s string) bool {
450
+ for _, ch := range s {
451
+ if ch < '0' || ch > '9' {
452
+ return false
453
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  }
455
+ return len(s) > 0
456
+ }
457
+
458
+ // 判断字符串是否包含英文字符
459
+ func hasEnglishLetter(s string) bool {
460
+ for i := 0; i < len(s); i++ {
461
+ if isEnglishLetter(s[i]) {
462
+ return true
463
+ }
464
  }
465
+ return false
466
  }
467
 
468
+ // 获取默认模型列表
469
+ func getDefaultModels() []Model {
470
+ return []Model{
471
+ {
472
+ ID: "GLM-4.5",
473
+ Object: "model",
474
+ Name: "GLM-4.5",
475
+ Created: time.Now().Unix(),
476
+ OwnedBy: "z.ai",
477
+ },
478
+ {
479
+ ID: "0727-360B-API",
480
+ Object: "model",
481
+ Name: "GLM-4.5",
482
+ Created: time.Now().Unix(),
483
+ OwnedBy: "z.ai",
484
+ },
 
 
485
  }
 
 
 
 
 
 
 
 
486
  }
487
 
488
+ // 处理统计页面请求
489
+ func handleStats(w http.ResponseWriter, r *http.Request) {
490
+ setCORSHeaders(w)
491
+ if r.Method == "OPTIONS" {
492
+ w.WriteHeader(http.StatusOK)
493
  return
494
  }
495
+
496
+ statsMutex.Lock()
497
+ currentStats := stats
498
+ statsMutex.Unlock()
499
+
500
+ requestsMutex.Lock()
501
+ currentRequests := make([]LiveRequest, len(liveRequests))
502
+ copy(currentRequests, liveRequests)
503
+ requestsMutex.Unlock()
504
+
505
+ // 计算运行时间
506
+ uptime := time.Since(startTime)
507
+
508
+ // 构建HTML页面
509
+ html := fmt.Sprintf(`
510
+ <!DOCTYPE html>
511
  <html lang="zh-CN">
512
  <head>
513
  <meta charset="UTF-8">
514
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
515
+ <title>OpenAI兼容服务统计信息</title>
516
  <style>
517
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
518
+ .container { max-width: 1200px; margin: 0 auto; }
519
+ .header { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
520
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 20px; }
521
+ .stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
522
+ .stat-value { font-size: 2em; font-weight: bold; color: #2563eb; }
523
+ .stat-label { color: #6b7280; margin-top: 5px; }
524
+ .requests-table { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
525
+ .table-header { background: #f9fafb; padding: 15px; font-weight: bold; border-bottom: 1px solid #e5e7eb; }
526
+ .table-row { padding: 10px 15px; border-bottom: 1px solid #f3f4f6; display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr; gap: 10px; }
527
+ .status-200 { color: #059669; }
528
+ .status-400, .status-401 { color: #dc2626; }
529
+ .status-500, .status-502 { color: #b91c1c; }
530
+ .refresh-btn { background: #2563eb; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; }
531
+ .refresh-btn:hover { background: #1d4ed8; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  </style>
533
+ <script>
534
+ function refreshPage() { location.reload(); }
535
+ setInterval(refreshPage, 30000); // 30秒自动刷新
536
+ </script>
537
  </head>
538
  <body>
539
  <div class="container">
540
+ <div class="header">
541
+ <h1>OpenAI兼容API服务器 - 统计信息</h1>
542
+ <p>服务运行时间: %s | 最后更新: %s</p>
543
+ <button class="refresh-btn" onclick="refreshPage()">刷新数据</button>
544
+ </div>
545
+
546
+ <div class="stats-grid">
547
  <div class="stat-card">
548
+ <div class="stat-value">%d</div>
549
  <div class="stat-label">总请求数</div>
550
  </div>
551
  <div class="stat-card">
552
+ <div class="stat-value">%d</div>
553
  <div class="stat-label">成功请求</div>
554
  </div>
555
  <div class="stat-card">
556
+ <div class="stat-value">%d</div>
557
  <div class="stat-label">失败请求</div>
558
  </div>
559
  <div class="stat-card">
560
+ <div class="stat-value">%.2fms</div>
561
  <div class="stat-label">平均响应时间</div>
562
  </div>
563
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
 
565
+ <div class="requests-table">
566
+ <div class="table-header">最近请求记录 (最多显示50条)</div>
567
+ <div class="table-row" style="font-weight: bold; background: #f9fafb;">
568
+ <div>时间</div>
569
+ <div>方法</div>
570
+ <div>路径</div>
571
+ <div>状态码</div>
572
+ <div>响应时间</div>
573
+ </div>`,
574
+ uptime.Round(time.Second),
575
+ time.Now().Format("2006-01-02 15:04:05"),
576
+ currentStats.TotalRequests,
577
+ currentStats.SuccessfulRequests,
578
+ currentStats.FailedRequests,
579
+ float64(currentStats.AverageResponseTime.Nanoseconds())/1000000,
580
+ )
581
+
582
+ // 添加最近的请求记录
583
+ for i := len(currentRequests) - 1; i >= 0 && len(currentRequests)-i <= 50; i-- {
584
+ req := currentRequests[i]
585
+ statusClass := "status-200"
586
+ if req.Status >= 400 && req.Status < 500 {
587
+ statusClass = "status-400"
588
+ } else if req.Status >= 500 {
589
+ statusClass = "status-500"
590
+ }
591
 
592
+ html += fmt.Sprintf(`
593
+ <div class="table-row">
594
+ <div>%s</div>
595
+ <div>%s</div>
596
+ <div>%s</div>
597
+ <div class="%s">%d</div>
598
+ <div>%.2fms</div>
599
+ </div>`,
600
+ req.Timestamp.Format("15:04:05"),
601
+ req.Method,
602
+ req.Path,
603
+ statusClass,
604
+ req.Status,
605
+ float64(req.Duration)/1000000, // Duration已经是纳秒,直接转换为毫秒
606
+ )
607
+ }
608
+
609
+ html += `
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
610
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
  </body>
613
  </html>`
 
 
 
614
 
615
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
616
+ w.WriteHeader(http.StatusOK)
617
+ w.Write([]byte(html))
 
 
 
 
 
 
 
 
 
 
 
618
  }
619
 
620
+ // 处理模型列表请求
621
  func handleModels(w http.ResponseWriter, r *http.Request) {
622
  setCORSHeaders(w)
623
  if r.Method == "OPTIONS" {
 
627
 
628
  response := ModelsResponse{
629
  Object: "list",
630
+ Data: getModels(),
631
  }
632
 
633
  w.Header().Set("Content-Type", "application/json")
634
  json.NewEncoder(w).Encode(response)
635
  }
636
 
637
+ // 设置CORS头
638
+ func setCORSHeaders(w http.ResponseWriter) {
639
+ w.Header().Set("Access-Control-Allow-Origin", "*")
640
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
641
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
642
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
643
+ }
644
+
645
+ // 处理聊天完成请求
646
  func handleChatCompletions(w http.ResponseWriter, r *http.Request) {
647
  startTime := time.Now()
648
  path := r.URL.Path
649
  clientIP := getClientIP(r)
650
  userAgent := r.UserAgent()
651
+
652
  setCORSHeaders(w)
653
  if r.Method == "OPTIONS" {
654
  w.WriteHeader(http.StatusOK)
655
  return
656
  }
657
+
658
  debugLog("收到chat completions请求")
659
 
660
+ //// 验证API Key(可选)
661
+ // authHeader := r.Header.Get("Authorization")
662
+ // if authHeader != "" {
663
+ // if !strings.HasPrefix(authHeader, "Bearer ") {
664
+ // debugLog("无效的Authorization头格式")
665
+ // http.Error(w, "Invalid Authorization header format", http.StatusUnauthorized)
666
+ // // 记录请求统计
667
+ // duration := time.Since(startTime)
668
+ // recordRequestStats(startTime, path, http.StatusUnauthorized)
669
+ // addLiveRequest(r.Method, path, http.StatusUnauthorized, duration, "", userAgent)
670
+ // return
671
+ // }
672
+
673
+ // apiKey := strings.TrimPrefix(authHeader, "Bearer ")
674
+ // if apiKey != DEFAULT_KEY {
675
+ // debugLog("无效的API key: %s", apiKey)
676
+ // http.Error(w, "Invalid API key", http.StatusUnauthorized)
677
+ // // 记录请求统计
678
+ // duration := time.Since(startTime)
679
+ // recordRequestStats(startTime, path, http.StatusUnauthorized)
680
+ // addLiveRequest(r.Method, path, http.StatusUnauthorized, duration, "", userAgent)
681
+ // return
682
+ // }
683
+ // debugLog("API key验证通过")
684
+ // } else {
685
+ // debugLog("无Authorization头,允许匿名访问")
686
+ // }
687
+
688
  // 读取请求体
689
  body, err := io.ReadAll(r.Body)
690
  if err != nil {
 
696
  addLiveRequest(r.Method, path, http.StatusBadRequest, duration, "", userAgent)
697
  return
698
  }
699
+
700
  // 解析请��
701
  var req OpenAIRequest
702
  if err := json.Unmarshal(body, &req); err != nil {
 
709
  return
710
  }
711
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
  // 如果客户端没有明确指定stream参数,使用默认值
713
  if !bytes.Contains(body, []byte(`"stream"`)) {
714
  req.Stream = DEFAULT_STREAM
715
  debugLog("客户端未指定stream参数,使用默认值: %v", DEFAULT_STREAM)
716
  }
717
+
718
+ debugLog("请求解析成功 - 模型: %s, 流式: %v, 消息数: %d", req.Model, req.Stream, len(req.Messages))
719
 
720
  // 生成会话相关ID
721
  chatID := fmt.Sprintf("%d-%d", time.Now().UnixNano(), time.Now().Unix())
722
  msgID := fmt.Sprintf("%d", time.Now().UnixNano())
723
 
724
+ // 构造上游请求
 
 
 
 
 
 
 
 
 
725
  upstreamReq := UpstreamRequest{
726
  Stream: true, // 总是使用流式从上游获取
727
  ChatID: chatID,
728
  ID: msgID,
729
+ Model: "0727-360B-API", // 上游实际模型ID
730
  Messages: req.Messages,
731
  Params: map[string]interface{}{},
732
  Features: map[string]interface{}{
733
+ "enable_thinking": true,
734
  },
735
  BackgroundTasks: map[string]bool{
736
  "title_generation": false,
 
741
  ID string `json:"id"`
742
  Name string `json:"name"`
743
  OwnedBy string `json:"owned_by"`
744
+ }{ID: "0727-360B-API", Name: "GLM-4.5", OwnedBy: "openai"},
745
  ToolServers: []string{},
746
  Variables: map[string]string{
747
  "{{USER_NAME}}": "User",
 
766
  }
767
  }
768
 
769
+ // 调用上游API
770
  if req.Stream {
771
+ handleStreamResponseWithIDs(w, upstreamReq, chatID, authToken, startTime, path, clientIP, userAgent)
772
  } else {
773
+ handleNonStreamResponseWithIDs(w, upstreamReq, chatID, authToken, startTime, path, clientIP, userAgent)
774
  }
775
  }
776
 
777
+ // 调用上游API并处理响应
778
  func callUpstreamWithHeaders(upstreamReq UpstreamRequest, refererChatID string, authToken string) (*http.Response, error) {
779
  reqBody, err := json.Marshal(upstreamReq)
780
  if err != nil {
781
  debugLog("上游请求序列化失败: %v", err)
782
  return nil, err
783
  }
784
+
785
  debugLog("调用上游API: %s", UPSTREAM_URL)
786
  debugLog("上游请求体: %s", string(reqBody))
787
+
788
  req, err := http.NewRequest("POST", UPSTREAM_URL, bytes.NewBuffer(reqBody))
789
  if err != nil {
790
  debugLog("创建HTTP请求失败: %v", err)
791
  return nil, err
792
  }
793
+
794
  req.Header.Set("Content-Type", "application/json")
795
  req.Header.Set("Accept", "application/json, text/event-stream")
796
  req.Header.Set("User-Agent", BROWSER_UA)
 
802
  req.Header.Set("X-FE-Version", X_FE_VERSION)
803
  req.Header.Set("Origin", ORIGIN_BASE)
804
  req.Header.Set("Referer", ORIGIN_BASE+"/c/"+refererChatID)
805
+
806
  client := &http.Client{Timeout: 60 * time.Second}
807
  resp, err := client.Do(req)
808
  if err != nil {
809
  debugLog("上游请求失败: %v", err)
810
  return nil, err
811
  }
812
+
813
  debugLog("上游响应状态: %d %s", resp.StatusCode, resp.Status)
814
  return resp, nil
815
  }
816
 
817
+ // 处理流式响应
818
+ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequest, chatID string, authToken string, startTime time.Time, path string, clientIP string, userAgent string) {
819
+ debugLog("开始处理流式响应 (chat_id=%s)", chatID)
820
+
821
  resp, err := callUpstreamWithHeaders(upstreamReq, chatID, authToken)
822
  if err != nil {
823
  debugLog("调用上游失败: %v", err)
 
829
  return
830
  }
831
  defer resp.Body.Close()
832
+
833
  if resp.StatusCode != http.StatusOK {
834
  debugLog("上游返回错误状态: %d", resp.StatusCode)
835
  // 读取错误响应体
 
864
  }
865
  // 处理每行前缀 "> "(包括起始位置)
866
  s = strings.TrimPrefix(s, "> ")
867
+ s = strings.ReplaceAll(s, "\n> ", "\n")
868
  return strings.TrimSpace(s)
869
  }
870
 
 
872
  w.Header().Set("Content-Type", "text/event-stream")
873
  w.Header().Set("Cache-Control", "no-cache")
874
  w.Header().Set("Connection", "keep-alive")
875
+
876
  flusher, ok := w.(http.Flusher)
877
  if !ok {
878
  http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
879
  return
880
  }
881
 
882
+ // 发送第一个chunk(role
883
  firstChunk := OpenAIResponse{
884
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
885
  Object: "chat.completion.chunk",
886
  Created: time.Now().Unix(),
887
+ Model: MODEL_NAME,
888
  Choices: []Choice{
889
  {
890
  Index: 0,
 
899
  debugLog("开始读取上游SSE流")
900
  scanner := bufio.NewScanner(resp.Body)
901
  lineCount := 0
902
+
903
  for scanner.Scan() {
904
  line := scanner.Text()
905
  lineCount++
906
+
907
  if !strings.HasPrefix(line, "data: ") {
908
  continue
909
  }
910
+
911
  dataStr := strings.TrimPrefix(line, "data: ")
912
  if dataStr == "" {
913
  continue
914
  }
915
+
916
  debugLog("收到SSE数据 (第%d行): %s", lineCount, dataStr)
917
+
918
  var upstreamData UpstreamData
919
  if err := json.Unmarshal([]byte(dataStr), &upstreamData); err != nil {
920
  debugLog("SSE数据解析失败: %v", err)
 
936
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
937
  Object: "chat.completion.chunk",
938
  Created: time.Now().Unix(),
939
+ Model: MODEL_NAME,
940
  Choices: []Choice{{Index: 0, Delta: Delta{}, FinishReason: "stop"}},
941
  }
942
  writeSSEChunk(w, endChunk)
943
+ fmt.Fprintf(w, "data: [DONE]\n\n")
944
  flusher.Flush()
945
  break
946
  }
 
960
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
961
  Object: "chat.completion.chunk",
962
  Created: time.Now().Unix(),
963
+ Model: MODEL_NAME,
964
  Choices: []Choice{
965
  {
966
  Index: 0,
 
981
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
982
  Object: "chat.completion.chunk",
983
  Created: time.Now().Unix(),
984
+ Model: MODEL_NAME,
985
  Choices: []Choice{
986
  {
987
  Index: 0,
 
992
  }
993
  writeSSEChunk(w, endChunk)
994
  flusher.Flush()
995
+
996
  // 发送[DONE]
997
+ fmt.Fprintf(w, "data: [DONE]\n\n")
998
  flusher.Flush()
999
  debugLog("流式响应完成,共处理%d行", lineCount)
1000
  break
1001
  }
1002
  }
1003
+
1004
  if err := scanner.Err(); err != nil {
1005
  debugLog("扫描器错误: %v", err)
1006
  }
 
1011
  addLiveRequest("POST", path, http.StatusOK, duration, "", userAgent)
1012
  }
1013
 
1014
+ // 写入SSE块
1015
  func writeSSEChunk(w http.ResponseWriter, chunk OpenAIResponse) {
1016
  data, _ := json.Marshal(chunk)
1017
+ fmt.Fprintf(w, "data: %s\n\n", data)
1018
  }
1019
 
1020
+ // 处理非流式响应
1021
+ func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequest, chatID string, authToken string, startTime time.Time, path string, clientIP string, userAgent string) {
1022
+ debugLog("开始处理非流式响应 (chat_id=%s)", chatID)
1023
+
1024
  resp, err := callUpstreamWithHeaders(upstreamReq, chatID, authToken)
1025
  if err != nil {
1026
  debugLog("调用上游失败: %v", err)
 
1032
  return
1033
  }
1034
  defer resp.Body.Close()
1035
+
1036
  if resp.StatusCode != http.StatusOK {
1037
  debugLog("上游返回错误状态: %d", resp.StatusCode)
1038
  // 读取错误响应体
 
1052
  var fullContent strings.Builder
1053
  scanner := bufio.NewScanner(resp.Body)
1054
  debugLog("开始收集完整响应内容")
1055
+
1056
  for scanner.Scan() {
1057
  line := scanner.Text()
1058
  if !strings.HasPrefix(line, "data: ") {
1059
  continue
1060
  }
1061
+
1062
  dataStr := strings.TrimPrefix(line, "data: ")
1063
  if dataStr == "" {
1064
  continue
1065
  }
1066
+
1067
  var upstreamData UpstreamData
1068
  if err := json.Unmarshal([]byte(dataStr), &upstreamData); err != nil {
1069
  continue
1070
  }
1071
+
1072
  if upstreamData.Data.DeltaContent != "" {
1073
  out := upstreamData.Data.DeltaContent
1074
  if upstreamData.Data.Phase == "thinking" {
 
1088
  s = strings.ReplaceAll(s, "</details>", "")
1089
  }
1090
  s = strings.TrimPrefix(s, "> ")
1091
+ s = strings.ReplaceAll(s, "\n> ", "\n")
1092
  return strings.TrimSpace(s)
1093
  }(out)
1094
  }
 
1096
  fullContent.WriteString(out)
1097
  }
1098
  }
1099
+
1100
  if upstreamData.Data.Done || upstreamData.Data.Phase == "done" {
1101
  debugLog("检测到完成信号,停止收集")
1102
  break
1103
  }
1104
  }
1105
+
1106
  finalContent := fullContent.String()
1107
  debugLog("内容收集完成,最终长度: %d", len(finalContent))
1108
 
1109
+ // 构造完整响应
1110
  response := OpenAIResponse{
1111
  ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
1112
  Object: "chat.completion",
1113
  Created: time.Now().Unix(),
1114
+ Model: MODEL_NAME,
1115
  Choices: []Choice{
1116
  {
1117
  Index: 0,
 
1128
  TotalTokens: 0,
1129
  },
1130
  }
1131
+
1132
  w.Header().Set("Content-Type", "application/json")
1133
  json.NewEncoder(w).Encode(response)
1134
  debugLog("非流式响应发送完成")
 
1138
  recordRequestStats(startTime, path, http.StatusOK)
1139
  addLiveRequest("POST", path, http.StatusOK, duration, "", userAgent)
1140
  }
1141
+
1142
+ // 处理OPTIONS请求
1143
+ func handleOptions(w http.ResponseWriter, r *http.Request) {
1144
+ setCORSHeaders(w)
1145
+ if r.Method == "OPTIONS" {
1146
+ w.WriteHeader(http.StatusOK)
1147
+ return
1148
+ }
1149
+ w.WriteHeader(http.StatusNotFound)
1150
+ }
1151
+
1152
+ func main() {
1153
+ // 注册路由
1154
+ http.HandleFunc("/v1/models", handleModels)
1155
+ http.HandleFunc("/v1/chat/completions", handleChatCompletions)
1156
+ http.HandleFunc("/api/v1/models", handleModels)
1157
+ http.HandleFunc("/api/v1/chat/completions", handleChatCompletions)
1158
+ http.HandleFunc("/stats", handleStats)
1159
+ http.HandleFunc("/", handleStats) // 首页显示统计信息
1160
+
1161
+ log.Printf("OpenAI兼容API服务器启动在端口%s", PORT)
1162
+ log.Printf("模型: %s", MODEL_NAME)
1163
+ log.Printf("上游: %s", UPSTREAM_URL)
1164
+ log.Printf("Debug模式: %v", DEBUG_MODE)
1165
+ log.Printf("默认流式响应: %v", DEFAULT_STREAM)
1166
+ log.Printf("匿名模式: %v", ANON_TOKEN_ENABLED)
1167
+ log.Printf("---------------------------------------------------------------------")
1168
+ log.Printf("🌐 服务器地址: http://localhost%s", PORT)
1169
+ log.Printf("📊 统计页面: http://localhost%s/stats", PORT)
1170
+ log.Printf("📋 模型列表: http://localhost%s/v1/models", PORT)
1171
+ log.Printf("💬 聊天接口: http://localhost%s/v1/chat/completions", PORT)
1172
+ log.Printf("---------------------------------------------------------------------")
1173
+ log.Fatal(http.ListenAndServe(PORT, nil))
1174
+ }