Spaces:
Running
Running
package main | |
import ( | |
"bufio" | |
"encoding/json" | |
"fmt" | |
"io" | |
"net/http" | |
"os" | |
"strings" | |
"time" | |
"github.com/google/uuid" | |
) | |
type OpenAIRequest struct { | |
Messages []Message `json:"messages"` | |
Stream bool `json:"stream"` | |
Model string `json:"model"` | |
} | |
type Message struct { | |
Role string `json:"role"` | |
Content string `json:"content"` | |
} | |
type MerlinRequest struct { | |
Attachments []interface{} `json:"attachments"` | |
ChatId string `json:"chatId"` | |
Language string `json:"language"` | |
Message struct { | |
Content string `json:"content"` | |
Context string `json:"context"` | |
ChildId string `json:"childId"` | |
Id string `json:"id"` | |
ParentId string `json:"parentId"` | |
} `json:"message"` | |
Metadata struct { | |
LargeContext bool `json:"largeContext"` | |
MerlinMagic bool `json:"merlinMagic"` | |
ProFinderMode bool `json:"proFinderMode"` | |
WebAccess bool `json:"webAccess"` | |
} `json:"metadata"` | |
Mode string `json:"mode"` | |
Model string `json:"model"` | |
} | |
type MerlinResponse struct { | |
Data struct { | |
Content string `json:"content"` | |
} `json:"data"` | |
} | |
type OpenAIResponse struct { | |
Id string `json:"id"` | |
Object string `json:"object"` | |
Created int64 `json:"created"` | |
Model string `json:"model"` | |
Choices []struct { | |
Delta struct { | |
Content string `json:"content"` | |
} `json:"delta"` | |
Index int `json:"index"` | |
FinishReason string `json:"finish_reason"` | |
} `json:"choices"` | |
} | |
type TokenResponse struct { | |
IdToken string `json:"idToken"` | |
} | |
func getEnvOrDefault(key, defaultValue string) string { | |
if value := os.Getenv(key); value != "" { | |
return value | |
} | |
return defaultValue | |
} | |
func getToken() (string, error) { | |
tokenReq := struct { | |
UUID string `json:"uuid"` | |
}{ | |
UUID: getEnvOrDefault("UUID", ""), | |
} | |
tokenReqBody, _ := json.Marshal(tokenReq) | |
resp, err := http.Post( | |
"https://getmerlin-main-server.vercel.app/generate", | |
"application/json", | |
strings.NewReader(string(tokenReqBody)), | |
) | |
if err != nil { | |
return "", err | |
} | |
defer resp.Body.Close() | |
var tokenResp TokenResponse | |
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { | |
return "", err | |
} | |
return tokenResp.IdToken, nil | |
} | |
func Handler(w http.ResponseWriter, r *http.Request) { | |
authToken := r.Header.Get("Authorization") | |
envToken := getEnvOrDefault("AUTH_TOKEN", "") | |
if envToken != "" && authToken != "Bearer "+envToken { | |
http.Error(w, "Unauthorized", http.StatusUnauthorized) | |
return | |
} | |
if r.URL.Path != "/hf/v1/chat/completions" { | |
w.Header().Set("Content-Type", "application/json") | |
w.WriteHeader(http.StatusOK) | |
fmt.Fprintf(w, `{"status":"GetMerlin2Api Service Running...","message":"MoLoveSze..."}`) | |
return | |
} | |
if r.Method != http.MethodPost { | |
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | |
return | |
} | |
var openAIReq OpenAIRequest | |
if err := json.NewDecoder(r.Body).Decode(&openAIReq); err != nil { | |
http.Error(w, err.Error(), http.StatusBadRequest) | |
return | |
} | |
var contextMessages []string | |
for i := 0; i < len(openAIReq.Messages)-1; i++ { | |
msg := openAIReq.Messages[i] | |
contextMessages = append(contextMessages, fmt.Sprintf("%s: %s", msg.Role, msg.Content)) | |
} | |
context := strings.Join(contextMessages, "\n") | |
merlinReq := MerlinRequest{ | |
Attachments: make([]interface{}, 0), | |
ChatId: generateV1UUID(), | |
Language: "AUTO", | |
Message: struct { | |
Content string `json:"content"` | |
Context string `json:"context"` | |
ChildId string `json:"childId"` | |
Id string `json:"id"` | |
ParentId string `json:"parentId"` | |
}{ | |
Content: openAIReq.Messages[len(openAIReq.Messages)-1].Content, | |
Context: context, | |
ChildId: generateUUID(), | |
Id: generateUUID(), | |
ParentId: "root", | |
}, | |
Mode: "UNIFIED_CHAT", | |
Model: openAIReq.Model, | |
Metadata: struct { | |
LargeContext bool `json:"largeContext"` | |
MerlinMagic bool `json:"merlinMagic"` | |
ProFinderMode bool `json:"proFinderMode"` | |
WebAccess bool `json:"webAccess"` | |
}{ | |
LargeContext: false, | |
MerlinMagic: false, | |
ProFinderMode: false, | |
WebAccess: false, | |
}, | |
} | |
token, err := getToken() | |
if err != nil { | |
http.Error(w, "Failed to get token: "+err.Error(), http.StatusInternalServerError) | |
return | |
} | |
client := &http.Client{} | |
merlinReqBody, _ := json.Marshal(merlinReq) | |
req, _ := http.NewRequest("POST", "https://arcane.getmerlin.in/v1/thread/unified", strings.NewReader(string(merlinReqBody))) | |
req.Header.Set("Content-Type", "application/json") | |
req.Header.Set("Accept", "text/event-stream, text/event-stream") | |
req.Header.Set("Authorization", "Bearer "+token) | |
req.Header.Set("x-merlin-version", "web-merlin") | |
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") | |
req.Header.Set("sec-ch-ua", `"Not(A:Brand";v="99", "Microsoft Edge";v="133", "Chromium";v="133"`) | |
req.Header.Set("sec-ch-ua-mobile", "?0") | |
req.Header.Set("sec-ch-ua-platform", "Windows") | |
req.Header.Set("Sec-Fetch-Site", "same-site") | |
req.Header.Set("Sec-Fetch-Mode", "cors") | |
req.Header.Set("Sec-Fetch-Dest", "empty") | |
req.Header.Set("host", "arcane.getmerlin.in") | |
var flusher http.Flusher | |
if openAIReq.Stream { | |
var ok bool | |
flusher, ok = w.(http.Flusher) | |
if !ok { | |
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) | |
return | |
} | |
w.Header().Set("Content-Type", "text/event-stream") | |
w.Header().Set("Cache-Control", "no-cache") | |
w.Header().Set("Connection", "keep-alive") | |
w.Header().Set("X-Accel-Buffering", "no") | |
w.Header().Set("Transfer-Encoding", "chunked") | |
defer func() { | |
if flusher != nil { | |
flusher.Flush() | |
} | |
}() | |
} else { | |
w.Header().Set("Content-Type", "application/json") | |
} | |
resp, err := client.Do(req) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
defer resp.Body.Close() | |
if !openAIReq.Stream { | |
var fullContent string | |
reader := bufio.NewReader(resp.Body) | |
for { | |
line, err := reader.ReadString('\n') | |
if err != nil { | |
if err == io.EOF { | |
break | |
} | |
continue | |
} | |
line = strings.TrimSpace(line) | |
if strings.HasPrefix(line, "event: message") { | |
dataLine, err := reader.ReadString('\n') | |
if err != nil { | |
continue | |
} | |
dataLine = strings.TrimSpace(dataLine) | |
if strings.HasPrefix(dataLine, "data: ") { | |
dataStr := strings.TrimPrefix(dataLine, "data: ") | |
var merlinResp MerlinResponse | |
if err := json.Unmarshal([]byte(dataStr), &merlinResp); err != nil { | |
continue | |
} | |
if merlinResp.Data.Content != " " { | |
fullContent += merlinResp.Data.Content | |
} | |
} | |
} | |
} | |
response := map[string]interface{}{ | |
"id": generateUUID(), | |
"object": "chat.completion", | |
"created": getCurrentTimestamp(), | |
"model": openAIReq.Model, | |
"choices": []map[string]interface{}{ | |
{ | |
"message": map[string]interface{}{ | |
"role": "assistant", | |
"content": fullContent, | |
}, | |
"finish_reason": "stop", | |
"index": 0, | |
}, | |
}, | |
} | |
json.NewEncoder(w).Encode(response) | |
return | |
} | |
reader := bufio.NewReader(resp.Body) | |
for { | |
line, err := reader.ReadString('\n') | |
if err != nil { | |
if err == io.EOF { | |
break | |
} | |
continue | |
} | |
if strings.HasPrefix(line, "event: message") { | |
dataLine, _ := reader.ReadString('\n') | |
var merlinResp MerlinResponse | |
json.Unmarshal([]byte(strings.TrimPrefix(dataLine, "data: ")), &merlinResp) | |
if merlinResp.Data.Content != "" { | |
openAIResp := OpenAIResponse{ | |
Id: generateUUID(), | |
Object: "chat.completion.chunk", | |
Created: getCurrentTimestamp(), | |
Model: openAIReq.Model, | |
Choices: []struct { | |
Delta struct { | |
Content string `json:"content"` | |
} `json:"delta"` | |
Index int `json:"index"` | |
FinishReason string `json:"finish_reason"` | |
}{{ | |
Delta: struct { | |
Content string `json:"content"` | |
}{ | |
Content: merlinResp.Data.Content, | |
}, | |
Index: 0, | |
FinishReason: "", | |
}}, | |
} | |
respData, _ := json.Marshal(openAIResp) | |
fmt.Fprintf(w, "data: %s\n\n", string(respData)) | |
flusher.Flush() | |
} | |
} | |
} | |
finalResp := OpenAIResponse{ | |
Id: generateUUID(), | |
Object: "chat.completion.chunk", | |
Created: getCurrentTimestamp(), | |
Model: openAIReq.Model, | |
Choices: []struct { | |
Delta struct { | |
Content string `json:"content"` | |
} `json:"delta"` | |
Index int `json:"index"` | |
FinishReason string `json:"finish_reason"` | |
}{{ | |
Delta: struct { | |
Content string `json:"content"` | |
}{Content: ""}, | |
Index: 0, | |
FinishReason: "stop", | |
}}, | |
} | |
respData, _ := json.Marshal(finalResp) | |
fmt.Fprintf(w, "data: %s\n\n", string(respData)) | |
fmt.Fprintf(w, "data: [DONE]\n\n") | |
flusher.Flush() | |
} | |
func generateUUID() string { | |
return uuid.New().String() | |
} | |
func generateV1UUID() string { | |
uuidObj := uuid.Must(uuid.NewUUID()) | |
return uuidObj.String() | |
} | |
func getCurrentTimestamp() int64 { | |
return time.Now().Unix() | |
} | |
func main() { | |
port := getEnvOrDefault("PORT", "7860") | |
http.HandleFunc("/", Handler) | |
fmt.Printf("Server starting on port %s...\n", port) | |
if err := http.ListenAndServe(":"+port, nil); err != nil { | |
fmt.Printf("Error starting server: %v\n", err) | |
} | |
} |