| package main |
|
|
| import ( |
| "bufio" |
| "crypto/md5" |
| "encoding/json" |
| "fmt" |
| "io" |
| "log" |
| "math/rand" |
| "net" |
| "net/http" |
| "os" |
| "regexp" |
| "slices" |
| "strings" |
| "sync" |
| "time" |
|
|
| "github.com/gin-gonic/gin" |
| "github.com/google/uuid" |
| "github.com/joho/godotenv" |
| ) |
|
|
| |
| type Message struct { |
| Role string `json:"role" binding:"required,oneof=system user assistant"` |
| Content string `json:"content" binding:"required"` |
| } |
|
|
| |
| type ChatCompletionRequest struct { |
| Model string `json:"model"` |
| Messages []Message `json:"messages"` |
| Stream bool `json:"stream"` |
| Temperature float64 `json:"temperature"` |
| TopP float64 `json:"top_p"` |
| PresencePenalty float64 `json:"presence_penalty"` |
| FrequencyPenalty float64 `json:"frequency_penalty"` |
| MaxTokens int `json:"max_tokens"` |
| } |
|
|
| |
| type Choice struct { |
| Index int `json:"index"` |
| Delta *Delta `json:"delta,omitempty"` |
| Message *Message `json:"message,omitempty"` |
| FinishReason *string `json:"finish_reason"` |
| } |
|
|
| |
| type Delta struct { |
| Role string `json:"role,omitempty"` |
| Content string `json:"content,omitempty"` |
| } |
|
|
| |
| type Usage struct { |
| PromptTokens int `json:"prompt_tokens"` |
| CompletionTokens int `json:"completion_tokens"` |
| TotalTokens int `json:"total_tokens"` |
| } |
|
|
| |
| type ChatCompletionResponse struct { |
| ID string `json:"id"` |
| Object string `json:"object"` |
| Created int64 `json:"created"` |
| Model string `json:"model"` |
| Choices []Choice `json:"choices"` |
| Usage *Usage `json:"usage,omitempty"` |
| } |
|
|
| |
| type ModelInfo struct { |
| ID string `json:"id"` |
| Object string `json:"object"` |
| Created int64 `json:"created"` |
| OwnedBy string `json:"owned_by"` |
| } |
|
|
| |
| type ModelList struct { |
| Object string `json:"object"` |
| Data []ModelInfo `json:"data"` |
| } |
|
|
| var ( |
| apiKey string |
| enableCORS bool |
| randomUA bool |
| supportedModels = []string{"DeepSeek-R1", "DeepSeek-V3"} |
| modelToConfig = map[string]map[string]interface{}{ |
| "DeepSeek-R1": {"model": "deepseek-huoshan", "isWebSearchEnabled": false}, |
| "DeepSeek-V3": {"model": "deepseek-guiji", "isWebSearchEnabled": false}, |
| } |
| apiDomain = "https://ai-chatbot.top" |
| defaultUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0" |
| deviceUAMap = make(map[string]string) |
| deviceUAMutex sync.Mutex |
|
|
| |
| userAgents = []string{ |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", |
| "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0", |
| "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15", |
| "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0", |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0", |
| } |
|
|
| |
| httpClient *http.Client |
|
|
| |
| chatIDCache struct { |
| sync.RWMutex |
| value string |
| expiresAt time.Time |
| } |
| ) |
|
|
| |
| func initHTTPClient() { |
| transport := &http.Transport{ |
| DialContext: (&net.Dialer{ |
| Timeout: 10 * time.Second, |
| KeepAlive: 30 * time.Second, |
| }).DialContext, |
| ForceAttemptHTTP2: true, |
| MaxIdleConns: 100, |
| MaxIdleConnsPerHost: 10, |
| IdleConnTimeout: 90 * time.Second, |
| TLSHandshakeTimeout: 10 * time.Second, |
| ExpectContinueTimeout: 1 * time.Second, |
| DisableKeepAlives: false, |
| } |
|
|
| httpClient = &http.Client{ |
| Transport: transport, |
| Timeout: 10 * time.Minute, |
| } |
| } |
|
|
| |
| func nanoid(size int) string { |
| alphabet := "abcdefgh0ijkl1mno2pqrs3tuv4wxyz5ABCDEFGH6IJKL7MNO8PQRS9TUV-WXYZ_" |
| b := make([]byte, size) |
| for i := range b { |
| b[i] = alphabet[rand.Intn(len(alphabet))] |
| } |
| return string(b) |
| } |
|
|
| |
| func generateDeviceID() string { |
| return fmt.Sprintf("%s_%s", uuid.New().String(), nanoid(20)) |
| } |
|
|
| |
| func getRandomUserAgent() string { |
| return userAgents[rand.Intn(len(userAgents))] |
| } |
|
|
| |
| func getUserAgent(deviceID string) string { |
| if !randomUA { |
| return defaultUA |
| } |
|
|
| deviceUAMutex.Lock() |
| defer deviceUAMutex.Unlock() |
| if ua, ok := deviceUAMap[deviceID]; ok { |
| return ua |
| } |
| |
| randomUserAgent := getRandomUserAgent() |
| deviceUAMap[deviceID] = randomUserAgent |
| return randomUserAgent |
| } |
|
|
| const signSalt = "@!~chatbot.0868" |
|
|
| |
| func generateSign(chatID string, timestamp int64) string { |
| msg := fmt.Sprintf("%s%d%s", chatID, timestamp, signSalt) |
| hash := md5.Sum([]byte(msg)) |
| return fmt.Sprintf("%x", hash) |
| } |
|
|
| |
| var chatIDOnce sync.Once |
|
|
| func getChatID() (string, error) { |
| |
| chatIDCache.RLock() |
| if time.Now().Before(chatIDCache.expiresAt) && chatIDCache.value != "" { |
| cachedID := chatIDCache.value |
| chatIDCache.RUnlock() |
| return cachedID, nil |
| } |
| chatIDCache.RUnlock() |
|
|
| |
| var err error |
| chatIDOnce.Do(func() { |
| |
| req, _ := http.NewRequest("GET", apiDomain+"/", nil) |
| req.Header.Set("User-Agent", defaultUA) |
| req.Header.Set("Accept", "*/*") |
| req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2") |
| req.Header.Set("Referer", "https://ai-chatbot.top/") |
|
|
| resp, err := httpClient.Do(req) |
| if err != nil { |
| return |
| } |
| defer resp.Body.Close() |
|
|
| body, _ := io.ReadAll(resp.Body) |
| reg := regexp.MustCompile(`[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}`) |
| ids := reg.FindAllString(string(body), -1) |
| if len(ids) == 0 { |
| err = fmt.Errorf("no chat id found") |
| return |
| } |
|
|
| chatID := ids[len(ids)-1] |
|
|
| |
| chatIDCache.Lock() |
| chatIDCache.value = chatID |
| chatIDCache.expiresAt = time.Now().Add(5 * time.Minute) |
| chatIDCache.Unlock() |
| }) |
|
|
| |
| chatIDCache.RLock() |
| defer chatIDCache.RUnlock() |
| return chatIDCache.value, err |
| } |
|
|
| |
| func preloadChatID() { |
| go func() { |
| for { |
| |
| sleepDuration := 4 * time.Minute |
| if !chatIDCache.expiresAt.IsZero() { |
| timeToExpiry := time.Until(chatIDCache.expiresAt) |
| if timeToExpiry > time.Minute { |
| sleepDuration = timeToExpiry - time.Minute |
| } |
| } |
|
|
| time.Sleep(sleepDuration) |
| getChatID() |
| } |
| }() |
| } |
|
|
| |
| type ChatbotFinish struct { |
| FinishReason string `json:"finishReason"` |
| Usage Usage `json:"usage"` |
| IsContinued bool `json:"isContinued"` |
| } |
|
|
| |
| func parseChatbotLine(line string) (string, string, error) { |
| if len(line) < 2 { |
| return "", "", fmt.Errorf("line too short") |
| } |
|
|
| colonIndex := strings.Index(line, ":") |
| if colonIndex == -1 { |
| return "", "", fmt.Errorf("no colon found") |
| } |
|
|
| prefix := line[:colonIndex] |
| content := line[colonIndex+1:] |
|
|
| return prefix, content, nil |
| } |
|
|
| |
| func handleStreamResponse(resp *http.Response, c *gin.Context, model string, startTime time.Time) { |
| defer resp.Body.Close() |
| |
| c.Header("Content-Type", "text/event-stream") |
| c.Header("Cache-Control", "no-cache") |
| c.Header("Connection", "keep-alive") |
|
|
| scanner := bufio.NewScanner(resp.Body) |
| created := time.Now().Unix() |
| chatID := fmt.Sprintf("chatcmpl-%s", nanoid(29)) |
|
|
| |
| var firstTokenTime time.Time |
| firstTokenReceived := false |
| thinking := false |
|
|
| |
| chunk := ChatCompletionResponse{ |
| ID: chatID, |
| Object: "chat.completion.chunk", |
| Created: created, |
| Model: model, |
| Choices: []Choice{{ |
| Index: 0, |
| Delta: &Delta{Content: ""}, |
| FinishReason: nil, |
| }}, |
| } |
| sendChunk(c, chunk) |
|
|
| for scanner.Scan() { |
| line := strings.TrimSpace(scanner.Text()) |
| if line == "" { |
| continue |
| } |
|
|
| prefix, content, err := parseChatbotLine(line) |
| if err != nil { |
| continue |
| } |
|
|
| switch prefix { |
| case "f": |
| |
| continue |
| case "g": |
| |
| if !thinking { |
| thinking = true |
| |
| if !firstTokenReceived { |
| firstTokenTime = time.Now() |
| firstTokenReceived = true |
| firstTokenDuration := firstTokenTime.Sub(startTime) |
| fmt.Printf("[INFO] First token received in: %v\n", firstTokenDuration) |
| } |
| |
| chunk := ChatCompletionResponse{ |
| ID: chatID, |
| Object: "chat.completion.chunk", |
| Created: created, |
| Model: model, |
| Choices: []Choice{{ |
| Index: 0, |
| Delta: &Delta{Content: "<think>"}, |
| FinishReason: nil, |
| }}, |
| } |
| sendChunk(c, chunk) |
| } |
|
|
| var contentStr string |
| if err := json.Unmarshal([]byte(content), &contentStr); err != nil { |
| continue |
| } |
| contentStr = strings.ReplaceAll(contentStr, "\\n", "\n") |
|
|
| chunk := ChatCompletionResponse{ |
| ID: chatID, |
| Object: "chat.completion.chunk", |
| Created: created, |
| Model: model, |
| Choices: []Choice{{ |
| Index: 0, |
| Delta: &Delta{Content: contentStr}, |
| FinishReason: nil, |
| }}, |
| } |
|
|
| jsonData, _ := json.Marshal(chunk) |
| fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData) |
| c.Writer.(http.Flusher).Flush() |
|
|
| case "0": |
| |
| |
| if !firstTokenReceived { |
| firstTokenTime = time.Now() |
| firstTokenReceived = true |
| firstTokenDuration := firstTokenTime.Sub(startTime) |
| fmt.Printf("[INFO] First token received in: %v\n", firstTokenDuration) |
| } |
|
|
| if thinking { |
| thinking = false |
| |
| chunk := ChatCompletionResponse{ |
| ID: chatID, |
| Object: "chat.completion.chunk", |
| Created: created, |
| Model: model, |
| Choices: []Choice{{ |
| Index: 0, |
| Delta: &Delta{Content: "</think>"}, |
| FinishReason: nil, |
| }}, |
| } |
| jsonData, _ := json.Marshal(chunk) |
| fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData) |
| c.Writer.(http.Flusher).Flush() |
| } |
|
|
| var contentStr string |
| if err := json.Unmarshal([]byte(content), &contentStr); err != nil { |
| continue |
| } |
| contentStr = strings.ReplaceAll(contentStr, "\\n", "\n") |
|
|
| chunk := ChatCompletionResponse{ |
| ID: chatID, |
| Object: "chat.completion.chunk", |
| Created: created, |
| Model: model, |
| Choices: []Choice{{ |
| Index: 0, |
| Delta: &Delta{Content: contentStr}, |
| FinishReason: nil, |
| }}, |
| } |
|
|
| jsonData, _ := json.Marshal(chunk) |
| fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData) |
| c.Writer.(http.Flusher).Flush() |
|
|
| case "e", "d": |
| |
| if thinking { |
| |
| chunk := ChatCompletionResponse{ |
| ID: chatID, |
| Object: "chat.completion.chunk", |
| Created: created, |
| Model: model, |
| Choices: []Choice{{ |
| Index: 0, |
| Delta: &Delta{Content: "</think>"}, |
| FinishReason: nil, |
| }}, |
| } |
| sendChunk(c, chunk) |
| } |
|
|
| var finishInfo ChatbotFinish |
| if err := json.Unmarshal([]byte(content), &finishInfo); err != nil { |
| continue |
| } |
|
|
| finishReason := finishInfo.FinishReason |
| chunk := ChatCompletionResponse{ |
| ID: chatID, |
| Object: "chat.completion.chunk", |
| Created: created, |
| Model: model, |
| Choices: []Choice{{ |
| Index: 0, |
| Delta: &Delta{}, |
| FinishReason: &finishReason, |
| }}, |
| Usage: &Usage{ |
| PromptTokens: finishInfo.Usage.PromptTokens, |
| CompletionTokens: finishInfo.Usage.CompletionTokens, |
| TotalTokens: finishInfo.Usage.PromptTokens + finishInfo.Usage.CompletionTokens, |
| }, |
| } |
|
|
| sendChunk(c, chunk) |
|
|
| |
| fmt.Fprintf(c.Writer, "data: [DONE]\n\n") |
| c.Writer.(http.Flusher).Flush() |
|
|
| |
| totalDuration := time.Since(startTime) |
| fmt.Printf("[INFO] Request completed in: %v\n", totalDuration) |
| return |
| } |
| } |
| } |
|
|
| |
| func handleNonStreamResponse(resp *http.Response, c *gin.Context, model string, startTime time.Time) { |
| scanner := bufio.NewScanner(resp.Body) |
| created := time.Now().Unix() |
| chatID := fmt.Sprintf("chatcmpl-%s", nanoid(29)) |
|
|
| var fullContent strings.Builder |
| var thinkContent strings.Builder |
| var usage *Usage |
|
|
| |
| var firstTokenTime time.Time |
| firstTokenReceived := false |
|
|
| for scanner.Scan() { |
| line := strings.TrimSpace(scanner.Text()) |
| if line == "" { |
| continue |
| } |
|
|
| prefix, content, err := parseChatbotLine(line) |
| if err != nil { |
| continue |
| } |
|
|
| switch prefix { |
| case "g": |
| |
| |
| if !firstTokenReceived { |
| firstTokenTime = time.Now() |
| firstTokenReceived = true |
| firstTokenDuration := firstTokenTime.Sub(startTime) |
| fmt.Printf("[INFO] First token received in: %v\n", firstTokenDuration) |
| } |
|
|
| var contentStr string |
| if err := json.Unmarshal([]byte(content), &contentStr); err != nil { |
| continue |
| } |
| contentStr = strings.ReplaceAll(contentStr, "\\n", "\n") |
| thinkContent.WriteString(contentStr) |
|
|
| case "0": |
| |
| |
| if !firstTokenReceived { |
| firstTokenTime = time.Now() |
| firstTokenReceived = true |
| firstTokenDuration := firstTokenTime.Sub(startTime) |
| fmt.Printf("[INFO] First token received in: %v\n", firstTokenDuration) |
| } |
|
|
| if thinkContent.Len() > 0 { |
| fullContent.WriteString("<think>") |
| fullContent.WriteString(thinkContent.String()) |
| fullContent.WriteString("</think>") |
| thinkContent.Reset() |
| } |
|
|
| var contentStr string |
| if err := json.Unmarshal([]byte(content), &contentStr); err != nil { |
| continue |
| } |
| contentStr = strings.ReplaceAll(contentStr, "\\n", "\n") |
| fullContent.WriteString(contentStr) |
|
|
| case "e", "d": |
| |
| var finishInfo ChatbotFinish |
| if err := json.Unmarshal([]byte(content), &finishInfo); err != nil { |
| continue |
| } |
|
|
| usage = &Usage{ |
| PromptTokens: finishInfo.Usage.PromptTokens, |
| CompletionTokens: finishInfo.Usage.CompletionTokens, |
| TotalTokens: finishInfo.Usage.PromptTokens + finishInfo.Usage.CompletionTokens, |
| } |
| } |
| } |
|
|
| |
| if thinkContent.Len() > 0 { |
| fullContent.WriteString("<think>") |
| fullContent.WriteString(thinkContent.String()) |
| fullContent.WriteString("</think>") |
| } |
|
|
| response := ChatCompletionResponse{ |
| ID: chatID, |
| Object: "chat.completion", |
| Created: created, |
| Model: model, |
| Choices: []Choice{{ |
| Index: 0, |
| Message: &Message{ |
| Role: "assistant", |
| Content: fullContent.String(), |
| }, |
| FinishReason: func() *string { s := "stop"; return &s }(), |
| }}, |
| Usage: usage, |
| } |
|
|
| |
| totalDuration := time.Since(startTime) |
| fmt.Printf("[INFO] Request completed in: %v\n", totalDuration) |
|
|
| sendChunk(c, ChatCompletionResponse{ |
| ID: response.ID, |
| Object: response.Object, |
| Created: response.Created, |
| Model: response.Model, |
| Choices: response.Choices, |
| Usage: response.Usage, |
| }) |
| } |
|
|
| |
| func verifyAPIKey(c *gin.Context) { |
| key := c.GetHeader("Authorization") |
|
|
| if key == "" { |
| c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"detail": "Missing Authorization header"}) |
| return |
| } |
|
|
| if strings.HasPrefix(key, "Bearer ") { |
| key = strings.TrimSpace(strings.TrimPrefix(key, "Bearer ")) |
| } |
|
|
| if key != apiKey { |
| c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"detail": "Invalid API key"}) |
| return |
| } |
|
|
| c.Next() |
| } |
|
|
| |
| func main() { |
| _ = godotenv.Load() |
| apiKey = os.Getenv("API_KEY") |
| fmt.Println("[DEBUG] Loaded API_KEY:", apiKey) |
| if apiKey == "" { |
| log.Fatal("API_KEY not found in .env file") |
| } |
|
|
| |
| randomUAStr := strings.ToLower(os.Getenv("RANDOM_UA")) |
| randomUA = randomUAStr == "true" || randomUAStr == "1" || randomUAStr == "yes" |
| fmt.Println("[DEBUG] RANDOM_UA enabled:", randomUA) |
|
|
| |
| initHTTPClient() |
|
|
| |
| preloadChatID() |
|
|
| enableCORS = true |
| gin.SetMode(gin.ReleaseMode) |
| r := gin.Default() |
|
|
| |
| if enableCORS { |
| r.Use(func(c *gin.Context) { |
| c.Writer.Header().Set("Access-Control-Allow-Origin", "*") |
| c.Writer.Header().Set("Access-Control-Allow-Methods", "*") |
| c.Writer.Header().Set("Access-Control-Allow-Headers", "*") |
| if c.Request.Method == "OPTIONS" { |
| c.AbortWithStatus(204) |
| return |
| } |
| c.Next() |
| }) |
| } |
|
|
| |
| r.GET("/health", func(c *gin.Context) { |
| chatID, err := getChatID() |
| status := "inactive" |
| if err == nil && chatID != "" { |
| status = "active" |
| } |
| c.JSON(http.StatusOK, gin.H{"status": "ok", "session": status}) |
| }) |
|
|
| |
| authorized := r.Group("/") |
| authorized.Use(verifyAPIKey) |
|
|
| |
| authorized.POST("/v1/chat/completions", func(c *gin.Context) { |
| |
| startTime := time.Now() |
|
|
| var req ChatCompletionRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
| return |
| } |
| if !contains(supportedModels, req.Model) { |
| req.Model = "DeepSeek-R1" |
| } |
| deviceID := generateDeviceID() |
| chatID, err := getChatID() |
| if err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get chat id"}) |
| return |
| } |
| timestamp := time.Now().UnixNano() / 1e6 |
| sign := generateSign(chatID, timestamp) |
|
|
| |
| messages := []map[string]string{} |
| for _, m := range req.Messages { |
| messages = append(messages, map[string]string{"role": m.Role, "content": m.Content}) |
| } |
| payload := map[string]interface{}{ |
| "id": chatID, |
| "messages": messages, |
| "selectedChatModel": modelToConfig[req.Model]["model"], |
| "isDeepThinkingEnabled": true, |
| "isWebSearchEnabled": modelToConfig[req.Model]["isWebSearchEnabled"], |
| } |
|
|
| |
| headers := map[string]string{ |
| "User-Agent": getUserAgent(deviceID), |
| "Accept": "*/*", |
| "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", |
| "Referer": "https://ai-chatbot.top/chat/" + chatID, |
| "Content-Type": "application/json", |
| "currentTime": fmt.Sprintf("%d", timestamp), |
| "sign": sign, |
| "Origin": "https://ai-chatbot.top", |
| "DNT": "1", |
| "Sec-GPC": "1", |
| "Connection": "keep-alive", |
| "Sec-Fetch-Dest": "empty", |
| "Sec-Fetch-Mode": "cors", |
| "Sec-Fetch-Site": "same-origin", |
| "Priority": "u=0", |
| } |
|
|
| |
| cookies := []*http.Cookie{ |
| {Name: "_ga_HVMZBNYJML", Value: "GS1.1.1742013194.1.1.1742013780.0.0.0"}, |
| {Name: "_ga", Value: "GA1.1.1029622546.1742013195"}, |
| } |
|
|
| |
| jsonBytes, _ := json.Marshal(payload) |
| req2, _ := http.NewRequest("POST", apiDomain+"/api/chat", strings.NewReader(string(jsonBytes))) |
| for k, v := range headers { |
| req2.Header.Set(k, v) |
| } |
| for _, ck := range cookies { |
| req2.AddCookie(ck) |
| } |
|
|
| resp, err := httpClient.Do(req2) |
| if err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to call ai-chatbot.top"}) |
| return |
| } |
| defer resp.Body.Close() |
|
|
| |
| if req.Stream { |
| handleStreamResponse(resp, c, req.Model, startTime) |
| } else { |
| handleNonStreamResponse(resp, c, req.Model, startTime) |
| } |
| }) |
|
|
| |
| authorized.GET("/v1/models", func(c *gin.Context) { |
| currentTime := time.Now().Unix() |
| models := []ModelInfo{} |
| for _, m := range supportedModels { |
| models = append(models, ModelInfo{ |
| ID: m, |
| Object: "model", |
| Created: currentTime, |
| OwnedBy: "aichatbot", |
| }) |
| } |
| c.JSON(http.StatusOK, ModelList{Object: "list", Data: models}) |
| }) |
|
|
| r.Run(":7860") |
| } |
|
|
| |
| func contains(arr []string, s string) bool { |
| return slices.Contains(arr, s) |
| } |
|
|
| |
| func sendChunk(c *gin.Context, chunk ChatCompletionResponse) { |
| jsonData, _ := json.Marshal(chunk) |
| fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData) |
| c.Writer.(http.Flusher).Flush() |
| } |
|
|