Files changed (1) hide show
  1. api/chat.go +416 -0
api/chat.go ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "bufio"
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "log"
9
+ "net/http"
10
+ "os"
11
+ "strings"
12
+ "time"
13
+
14
+ "github.com/google/uuid"
15
+ )
16
+
17
+ type OpenAIRequest struct {
18
+ Messages []Message `json:"messages"`
19
+ Stream bool `json:"stream"`
20
+ Model string `json:"model"`
21
+ }
22
+
23
+ type Message struct {
24
+ Role string `json:"role"`
25
+ Content string `json:"content"`
26
+ }
27
+
28
+ type MerlinRequest struct {
29
+ Attachments []interface{} `json:"attachments"`
30
+ ChatId string `json:"chatId"`
31
+ Language string `json:"language"`
32
+ Message struct {
33
+ Content string `json:"content"`
34
+ Context string `json:"context"`
35
+ ChildId string `json:"childId"`
36
+ Id string `json:"id"`
37
+ ParentId string `json:"parentId"`
38
+ } `json:"message"`
39
+ Metadata struct {
40
+ LargeContext bool `json:"largeContext"`
41
+ MerlinMagic bool `json:"merlinMagic"`
42
+ ProFinderMode bool `json:"proFinderMode"`
43
+ WebAccess bool `json:"webAccess"`
44
+ } `json:"metadata"`
45
+ Mode string `json:"mode"`
46
+ Model string `json:"model"`
47
+ }
48
+
49
+ type MerlinResponse struct {
50
+ Data struct {
51
+ Content string `json:"content"`
52
+ } `json:"data"`
53
+ }
54
+
55
+ type OpenAIResponse struct {
56
+ Id string `json:"id"`
57
+ Object string `json:"object"`
58
+ Created int64 `json:"created"`
59
+ Model string `json:"model"`
60
+ Choices []struct {
61
+ Delta struct {
62
+ Content string `json:"content"`
63
+ } `json:"delta"`
64
+ Index int `json:"index"`
65
+ FinishReason string `json:"finish_reason"`
66
+ } `json:"choices"`
67
+ }
68
+
69
+ type TokenResponse struct {
70
+ IdToken string `json:"idToken"`
71
+ }
72
+
73
+ const (
74
+ maxRetries = 3
75
+ retryInterval = 2 * time.Second
76
+ )
77
+
78
+ func getEnvOrDefault(key, defaultValue string) string {
79
+ if value := os.Getenv(key); value != "" {
80
+ return value
81
+ }
82
+ return defaultValue
83
+ }
84
+
85
+ func getToken() (string, error) {
86
+ tokenReq := struct {
87
+ UUID string `json:"uuid"`
88
+ }{
89
+ UUID: getEnvOrDefault("UUID", ""),
90
+ }
91
+
92
+ tokenReqBody, _ := json.Marshal(tokenReq)
93
+ resp, err := http.Post(
94
+ "https://getmerlin-main-server.vercel.app/generate",
95
+ "application/json",
96
+ strings.NewReader(string(tokenReqBody)),
97
+ )
98
+ if err != nil {
99
+ log.Printf("获取 token 失败: %v", err)
100
+ return "", err
101
+ }
102
+ defer resp.Body.Close()
103
+
104
+ var tokenResp TokenResponse
105
+ if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
106
+ log.Printf("解析 token 响应失败: %v", err)
107
+ return "", err
108
+ }
109
+
110
+ return tokenResp.IdToken, nil
111
+ }
112
+
113
+ func callMerlinAPI(client *http.Client, req *http.Request, requestID string) (*http.Response, error) {
114
+ var lastErr error
115
+ for i := 0; i < maxRetries; i++ {
116
+ if i > 0 {
117
+ log.Printf("[%s] 第 %d 次重试调用 Merlin API", requestID, i+1)
118
+ time.Sleep(retryInterval)
119
+ }
120
+
121
+ resp, err := client.Do(req)
122
+ if err != nil {
123
+ lastErr = err
124
+ continue
125
+ }
126
+
127
+ return resp, nil
128
+ }
129
+ return nil, fmt.Errorf("达到最大重试次数 (%d): %v", maxRetries, lastErr)
130
+ }
131
+
132
+ func processMerlinStreamResponse(reader *bufio.Reader, requestID string) (string, bool, error) {
133
+ var content string
134
+ var hasContent bool
135
+
136
+ for i := 0; i < maxRetries; i++ {
137
+ if i > 0 {
138
+ log.Printf("[%s] 第 %d 次重试处理响应", requestID, i+1)
139
+ time.Sleep(retryInterval)
140
+ }
141
+
142
+ line, err := reader.ReadString('\n')
143
+ if err != nil {
144
+ if err == io.EOF {
145
+ break
146
+ }
147
+ continue
148
+ }
149
+
150
+ if strings.HasPrefix(line, "event: message") {
151
+ dataLine, err := reader.ReadString('\n')
152
+ if err != nil {
153
+ continue
154
+ }
155
+ dataLine = strings.TrimSpace(dataLine)
156
+
157
+ if strings.HasPrefix(dataLine, "data: ") {
158
+ dataStr := strings.TrimPrefix(dataLine, "data: ")
159
+ var merlinResp MerlinResponse
160
+ if err := json.Unmarshal([]byte(dataStr), &merlinResp); err != nil {
161
+ continue
162
+ }
163
+ if merlinResp.Data.Content != " " {
164
+ content += merlinResp.Data.Content
165
+ hasContent = true
166
+ return content, hasContent, nil
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ if !hasContent {
173
+ return "", false, fmt.Errorf("响应内容为空")
174
+ }
175
+
176
+ return content, true, nil
177
+ }
178
+
179
+ func Handler(w http.ResponseWriter, r *http.Request) {
180
+ requestID := generateUUID()
181
+
182
+ authToken := r.Header.Get("Authorization")
183
+ envToken := getEnvOrDefault("AUTH_TOKEN", "")
184
+
185
+ if envToken != "" && authToken != "Bearer "+envToken {
186
+ log.Printf("[%s] 认证失败: 无效的 token", requestID)
187
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
188
+ return
189
+ }
190
+
191
+ if r.URL.Path != "/hf/v1/chat/completions" {
192
+ w.Header().Set("Content-Type", "application/json")
193
+ w.WriteHeader(http.StatusOK)
194
+ fmt.Fprintf(w, `{"status":"GetMerlin2Api Service Running...","message":"MoLoveSze..."}`)
195
+ return
196
+ }
197
+
198
+ if r.Method != http.MethodPost {
199
+ log.Printf("[%s] 不支持的请求方法: %s", requestID, r.Method)
200
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
201
+ return
202
+ }
203
+
204
+ var openAIReq OpenAIRequest
205
+ if err := json.NewDecoder(r.Body).Decode(&openAIReq); err != nil {
206
+ log.Printf("[%s] 解析请求体失败: %v", requestID, err)
207
+ http.Error(w, err.Error(), http.StatusBadRequest)
208
+ return
209
+ }
210
+
211
+ var contextMessages []string
212
+ for i := 0; i < len(openAIReq.Messages)-1; i++ {
213
+ msg := openAIReq.Messages[i]
214
+ contextMessages = append(contextMessages, fmt.Sprintf("%s: %s", msg.Role, msg.Content))
215
+ }
216
+ context := strings.Join(contextMessages, "\n")
217
+ merlinReq := MerlinRequest{
218
+ Attachments: make([]interface{}, 0),
219
+ ChatId: generateV1UUID(),
220
+ Language: "AUTO",
221
+ Message: struct {
222
+ Content string `json:"content"`
223
+ Context string `json:"context"`
224
+ ChildId string `json:"childId"`
225
+ Id string `json:"id"`
226
+ ParentId string `json:"parentId"`
227
+ }{
228
+ Content: openAIReq.Messages[len(openAIReq.Messages)-1].Content,
229
+ Context: context,
230
+ ChildId: generateUUID(),
231
+ Id: generateUUID(),
232
+ ParentId: "root",
233
+ },
234
+ Mode: "UNIFIED_CHAT",
235
+ Model: openAIReq.Model,
236
+ Metadata: struct {
237
+ LargeContext bool `json:"largeContext"`
238
+ MerlinMagic bool `json:"merlinMagic"`
239
+ ProFinderMode bool `json:"proFinderMode"`
240
+ WebAccess bool `json:"webAccess"`
241
+ }{
242
+ LargeContext: false,
243
+ MerlinMagic: false,
244
+ ProFinderMode: false,
245
+ WebAccess: false,
246
+ },
247
+ }
248
+ token, err := getToken()
249
+ if err != nil {
250
+ log.Printf("[%s] 获取 token 失败: %v", requestID, err)
251
+ http.Error(w, "Failed to get token: "+err.Error(), http.StatusInternalServerError)
252
+ return
253
+ }
254
+ client := &http.Client{}
255
+ merlinReqBody, _ := json.Marshal(merlinReq)
256
+
257
+ req, _ := http.NewRequest("POST", "https://arcane.getmerlin.in/v1/thread/unified", strings.NewReader(string(merlinReqBody)))
258
+ req.Header.Set("Content-Type", "application/json")
259
+ req.Header.Set("Accept", "text/event-stream, text/event-stream")
260
+ req.Header.Set("Authorization", "Bearer "+token)
261
+ req.Header.Set("x-merlin-version", "web-merlin")
262
+ req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36")
263
+ req.Header.Set("sec-ch-ua", `"Not(A:Brand";v="99", "Microsoft Edge";v="133", "Chromium";v="133"`)
264
+ req.Header.Set("sec-ch-ua-mobile", "?0")
265
+ req.Header.Set("sec-ch-ua-platform", "Windows")
266
+ req.Header.Set("Sec-Fetch-Site", "same-site")
267
+ req.Header.Set("Sec-Fetch-Mode", "cors")
268
+ req.Header.Set("Sec-Fetch-Dest", "empty")
269
+ req.Header.Set("host", "arcane.getmerlin.in")
270
+ if openAIReq.Stream {
271
+ w.Header().Set("Content-Type", "text/event-stream")
272
+ w.Header().Set("Cache-Control", "no-cache")
273
+ w.Header().Set("Connection", "keep-alive")
274
+ w.Header().Set("X-Accel-Buffering", "no")
275
+ w.Header().Set("Transfer-Encoding", "chunked")
276
+ } else {
277
+ w.Header().Set("Content-Type", "application/json")
278
+ }
279
+
280
+ resp, err := callMerlinAPI(client, req, requestID)
281
+ if err != nil {
282
+ log.Printf("[%s] 调用 Merlin API 失败: %v", requestID, err)
283
+ http.Error(w, err.Error(), http.StatusInternalServerError)
284
+ return
285
+ }
286
+ defer resp.Body.Close()
287
+
288
+ if !openAIReq.Stream {
289
+ reader := bufio.NewReader(resp.Body)
290
+ content, hasContent, err := processMerlinStreamResponse(reader, requestID)
291
+
292
+ if err != nil {
293
+ log.Printf("[%s] %v", requestID, err)
294
+ http.Error(w, "Failed to process response", http.StatusInternalServerError)
295
+ return
296
+ }
297
+
298
+ if !hasContent {
299
+ log.Printf("[%s] 所有重试后仍然响应内容为空", requestID)
300
+ }
301
+
302
+ response := map[string]interface{}{
303
+ "id": generateUUID(),
304
+ "object": "chat.completion",
305
+ "created": getCurrentTimestamp(),
306
+ "model": openAIReq.Model,
307
+ "choices": []map[string]interface{}{
308
+ {
309
+ "message": map[string]interface{}{
310
+ "role": "assistant",
311
+ "content": content,
312
+ },
313
+ "finish_reason": "stop",
314
+ "index": 0,
315
+ },
316
+ },
317
+ }
318
+ json.NewEncoder(w).Encode(response)
319
+ return
320
+ }
321
+
322
+ reader := bufio.NewReaderSize(resp.Body, 256)
323
+ var streamHasContent bool
324
+ for {
325
+ line, err := reader.ReadString('\n')
326
+ if err != nil {
327
+ if err == io.EOF {
328
+ break
329
+ }
330
+ continue
331
+ }
332
+
333
+ if strings.HasPrefix(line, "event: message") {
334
+ dataLine, _ := reader.ReadString('\n')
335
+ var merlinResp MerlinResponse
336
+ json.Unmarshal([]byte(strings.TrimPrefix(dataLine, "data: ")), &merlinResp)
337
+
338
+ if merlinResp.Data.Content != "" {
339
+ streamHasContent = true
340
+ openAIResp := OpenAIResponse{
341
+ Id: generateUUID(),
342
+ Object: "chat.completion.chunk",
343
+ Created: getCurrentTimestamp(),
344
+ Model: openAIReq.Model,
345
+ Choices: []struct {
346
+ Delta struct {
347
+ Content string `json:"content"`
348
+ } `json:"delta"`
349
+ Index int `json:"index"`
350
+ FinishReason string `json:"finish_reason"`
351
+ }{{
352
+ Delta: struct {
353
+ Content string `json:"content"`
354
+ }{
355
+ Content: merlinResp.Data.Content,
356
+ },
357
+ Index: 0,
358
+ FinishReason: "",
359
+ }},
360
+ }
361
+
362
+ respData, _ := json.Marshal(openAIResp)
363
+ fmt.Fprintf(w, "data: %s\n\n", string(respData))
364
+ }
365
+ }
366
+ }
367
+
368
+ if !streamHasContent {
369
+ log.Printf("[%s] Merlin API 流式响应内容为空", requestID)
370
+ }
371
+
372
+ finalResp := OpenAIResponse{
373
+ Id: generateUUID(),
374
+ Object: "chat.completion.chunk",
375
+ Created: getCurrentTimestamp(),
376
+ Model: openAIReq.Model,
377
+ Choices: []struct {
378
+ Delta struct {
379
+ Content string `json:"content"`
380
+ } `json:"delta"`
381
+ Index int `json:"index"`
382
+ FinishReason string `json:"finish_reason"`
383
+ }{{
384
+ Delta: struct {
385
+ Content string `json:"content"`
386
+ }{Content: ""},
387
+ Index: 0,
388
+ FinishReason: "stop",
389
+ }},
390
+ }
391
+ respData, _ := json.Marshal(finalResp)
392
+ fmt.Fprintf(w, "data: %s\n\n", string(respData))
393
+ fmt.Fprintf(w, "data: [DONE]\n\n")
394
+ }
395
+
396
+ func generateUUID() string {
397
+ return uuid.New().String()
398
+ }
399
+
400
+ func generateV1UUID() string {
401
+ uuidObj := uuid.Must(uuid.NewUUID())
402
+ return uuidObj.String()
403
+ }
404
+
405
+ func getCurrentTimestamp() int64 {
406
+ return time.Now().Unix()
407
+ }
408
+
409
+ func main() {
410
+ port := getEnvOrDefault("PORT", "7860")
411
+ http.HandleFunc("/", Handler)
412
+ fmt.Printf("Server starting on port %s...\n", port)
413
+ if err := http.ListenAndServe(":"+port, nil); err != nil {
414
+ fmt.Printf("Error starting server: %v\n", err)
415
+ }
416
+ }