isididiidid commited on
Commit
8f12dc0
·
verified ·
1 Parent(s): 702981f

Create api/chat.go

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