|
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) |
|
} |
|
} |