package controller
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"io"
"kilo2api/common"
"kilo2api/common/config"
logger "kilo2api/common/loggger"
"kilo2api/cycletls"
"kilo2api/kilo-api"
"kilo2api/model"
"net/http"
"strings"
"time"
)
const (
errServerErrMsg = "Service Unavailable"
responseIDFormat = "chatcmpl-%s"
)
// ChatForOpenAI @Summary OpenAI对话接口
// @Description OpenAI对话接口
// @Tags OpenAI
// @Accept json
// @Produce json
// @Param req body model.OpenAIChatCompletionRequest true "OpenAI对话请求"
// @Param Authorization header string true "Authorization API-KEY"
// @Router /v1/chat/completions [post]
func ChatForOpenAI(c *gin.Context) {
client := cycletls.Init()
defer safeClose(client)
var openAIReq model.OpenAIChatCompletionRequest
if err := c.BindJSON(&openAIReq); err != nil {
logger.Errorf(c.Request.Context(), err.Error())
c.JSON(http.StatusInternalServerError, model.OpenAIErrorResponse{
OpenAIError: model.OpenAIError{
Message: "Invalid request parameters",
Type: "request_error",
Code: "500",
},
})
return
}
openAIReq.RemoveEmptyContentMessages()
modelInfo, b := common.GetModelInfo(openAIReq.Model)
if !b {
c.JSON(http.StatusBadRequest, model.OpenAIErrorResponse{
OpenAIError: model.OpenAIError{
Message: fmt.Sprintf("Model %s not supported", openAIReq.Model),
Type: "invalid_request_error",
Code: "invalid_model",
},
})
return
}
if openAIReq.MaxTokens > modelInfo.MaxTokens {
c.JSON(http.StatusBadRequest, model.OpenAIErrorResponse{
OpenAIError: model.OpenAIError{
Message: fmt.Sprintf("Max tokens %d exceeds limit %d", openAIReq.MaxTokens, modelInfo.MaxTokens),
Type: "invalid_request_error",
Code: "invalid_max_tokens",
},
})
return
}
if openAIReq.Stream {
handleStreamRequest(c, client, openAIReq, modelInfo)
} else {
handleNonStreamRequest(c, client, openAIReq, modelInfo)
}
}
func handleNonStreamRequest(c *gin.Context, client cycletls.CycleTLS, openAIReq model.OpenAIChatCompletionRequest, modelInfo common.ModelInfo) {
ctx := c.Request.Context()
cookieManager := config.NewCookieManager()
maxRetries := len(cookieManager.Cookies)
cookie, err := cookieManager.GetRandomCookie()
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
for attempt := 0; attempt < maxRetries; attempt++ {
requestBody, err := createRequestBody(c, &openAIReq, modelInfo)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
jsonData, err := json.Marshal(requestBody)
if err != nil {
c.JSON(500, gin.H{"error": "Failed to marshal request body"})
return
}
sseChan, err := kilo_api.MakeStreamChatRequest(c, client, jsonData, cookie, modelInfo)
if err != nil {
logger.Errorf(ctx, "MakeStreamChatRequest err on attempt %d: %v", attempt+1, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
isRateLimit := false
var delta string
var assistantMsgContent string
var shouldContinue bool
thinkStartType := new(bool)
thinkEndType := new(bool)
SSELoop:
for response := range sseChan {
data := response.Data
if data == "" {
continue
}
if response.Done {
switch {
case common.IsUsageLimitExceeded(data):
if config.CheatEnabled {
split := strings.Split(cookie, "=")
if len(split) == 2 {
cookieSession := split[1]
cheatResp, err := client.Do(config.CheatUrl, cycletls.Options{
Timeout: 10 * 60 * 60,
Proxy: config.ProxyUrl, // 在每个请求中设置代理
Body: "",
Headers: map[string]string{
"Cookie": cookieSession,
},
}, "POST")
if err != nil {
logger.Errorf(ctx, "Cheat err Cookie: %s err: %v", cookie, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if cheatResp.Status == 200 {
logger.Debug(c, fmt.Sprintf("Cheat Success Cookie: %s", cookie))
attempt-- // 抵消循环结束时的attempt++
break SSELoop
}
if cheatResp.Status == 402 {
logger.Warnf(ctx, "Cookie Unlink Card Cookie: %s", cookie)
} else {
logger.Errorf(ctx, "Cheat err Cookie: %s Resp: %v", cookie, cheatResp.Body)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Cheat Resp.Status:%v Resp.Body:%v", cheatResp.Status, cheatResp.Body)})
return
}
}
}
isRateLimit = true
logger.Warnf(ctx, "Cookie Usage limit exceeded, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie)
config.RemoveCookie(cookie)
break SSELoop
case common.IsServerError(data):
logger.Errorf(ctx, errServerErrMsg)
c.JSON(http.StatusInternalServerError, gin.H{"error": errServerErrMsg})
return
case common.IsNotLogin(data):
isRateLimit = true
logger.Warnf(ctx, "Cookie Not Login, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie)
break SSELoop
case common.IsRateLimit(data):
isRateLimit = true
logger.Warnf(ctx, "Cookie rate limited, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie)
config.AddRateLimitCookie(cookie, time.Now().Add(time.Duration(config.RateLimitCookieLockDuration)*time.Second))
break SSELoop
}
logger.Warnf(ctx, response.Data)
return
}
logger.Debug(ctx, strings.TrimSpace(data))
streamDelta, streamShouldContinue := processNoStreamData(c, data, modelInfo, thinkStartType, thinkEndType)
delta = streamDelta
shouldContinue = streamShouldContinue
// 处理事件流数据
if !shouldContinue {
promptTokens := model.CountTokenText(string(jsonData), openAIReq.Model)
completionTokens := model.CountTokenText(assistantMsgContent, openAIReq.Model)
finishReason := "stop"
c.JSON(http.StatusOK, model.OpenAIChatCompletionResponse{
ID: fmt.Sprintf(responseIDFormat, time.Now().Format("20060102150405")),
Object: "chat.completion",
Created: time.Now().Unix(),
Model: openAIReq.Model,
Choices: []model.OpenAIChoice{{
Message: model.OpenAIMessage{
Role: "assistant",
Content: assistantMsgContent,
},
FinishReason: &finishReason,
}},
Usage: model.OpenAIUsage{
PromptTokens: promptTokens,
CompletionTokens: completionTokens,
TotalTokens: promptTokens + completionTokens,
},
})
return
} else {
assistantMsgContent = assistantMsgContent + delta
}
}
if !isRateLimit {
return
}
// 获取下一个可用的cookie继续尝试
cookie, err = cookieManager.GetNextCookie()
if err != nil {
logger.Errorf(ctx, "No more valid cookies available after attempt %d", attempt+1)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
logger.Errorf(ctx, "All cookies exhausted after %d attempts", maxRetries)
c.JSON(http.StatusInternalServerError, gin.H{"error": "All cookies are temporarily unavailable."})
return
}
func createRequestBody(c *gin.Context, openAIReq *model.OpenAIChatCompletionRequest, modelInfo common.ModelInfo) (map[string]interface{}, error) {
client := cycletls.Init()
defer safeClose(client)
if config.PRE_MESSAGES_JSON != "" {
err := openAIReq.PrependMessagesFromJSON(config.PRE_MESSAGES_JSON)
if err != nil {
return nil, fmt.Errorf("PrependMessagesFromJSON err: %v JSON:%s", err, config.PRE_MESSAGES_JSON)
}
}
if openAIReq.MaxTokens <= 1 {
openAIReq.MaxTokens = 8192
}
var data []byte
var err error
if modelInfo.Source == "claude" {
claudeRequest, err := model.ConvertOpenAIToClaudeRequest(*openAIReq, modelInfo)
if err != nil {
return nil, fmt.Errorf("ConvertOpenAIToClaudeRequest err: %v", err)
}
data, err = json.Marshal(claudeRequest)
if err != nil {
return nil, err
}
} else if modelInfo.Source == "openrouter" {
geminiReq, err := model.ConvertOpenAIToGeminiRequest(*openAIReq, modelInfo)
if err != nil {
return nil, fmt.Errorf("ConvertOpenAIToGeminiRequest err: %v", err)
}
data, err = json.Marshal(geminiReq)
if err != nil {
return nil, err
}
}
requestBody := make(map[string]interface{})
err = json.Unmarshal(data, &requestBody)
if err != nil {
return nil, err
}
// 创建请求体
logger.Debug(c.Request.Context(), fmt.Sprintf("RequestBody: %v", requestBody))
return requestBody, nil
}
// createStreamResponse 创建流式响应
func createStreamResponse(responseId, modelName string, jsonData []byte, delta model.OpenAIDelta, finishReason *string) model.OpenAIChatCompletionResponse {
promptTokens := model.CountTokenText(string(jsonData), modelName)
completionTokens := model.CountTokenText(delta.Content, modelName)
return model.OpenAIChatCompletionResponse{
ID: responseId,
Object: "chat.completion.chunk",
Created: time.Now().Unix(),
Model: modelName,
Choices: []model.OpenAIChoice{
{
Index: 0,
Delta: delta,
FinishReason: finishReason,
},
},
Usage: model.OpenAIUsage{
PromptTokens: promptTokens,
CompletionTokens: completionTokens,
TotalTokens: promptTokens + completionTokens,
},
}
}
// handleDelta 处理消息字段增量
func handleDelta(c *gin.Context, delta string, responseId, modelName string, jsonData []byte) error {
// 创建基础响应
createResponse := func(content string) model.OpenAIChatCompletionResponse {
return createStreamResponse(
responseId,
modelName,
jsonData,
model.OpenAIDelta{Content: content, Role: "assistant"},
nil,
)
}
// 发送基础事件
var err error
if err = sendSSEvent(c, createResponse(delta)); err != nil {
return err
}
return err
}
// handleMessageResult 处理消息结果
func handleMessageResult(c *gin.Context, responseId, modelName string, jsonData []byte) bool {
finishReason := "stop"
var delta string
promptTokens := 0
completionTokens := 0
streamResp := createStreamResponse(responseId, modelName, jsonData, model.OpenAIDelta{Content: delta, Role: "assistant"}, &finishReason)
streamResp.Usage = model.OpenAIUsage{
PromptTokens: promptTokens,
CompletionTokens: completionTokens,
TotalTokens: promptTokens + completionTokens,
}
if err := sendSSEvent(c, streamResp); err != nil {
logger.Warnf(c.Request.Context(), "sendSSEvent err: %v", err)
return false
}
c.SSEvent("", " [DONE]")
return false
}
// sendSSEvent 发送SSE事件
func sendSSEvent(c *gin.Context, response model.OpenAIChatCompletionResponse) error {
jsonResp, err := json.Marshal(response)
if err != nil {
logger.Errorf(c.Request.Context(), "Failed to marshal response: %v", err)
return err
}
c.SSEvent("", " "+string(jsonResp))
c.Writer.Flush()
return nil
}
func handleStreamRequest(c *gin.Context, client cycletls.CycleTLS, openAIReq model.OpenAIChatCompletionRequest, modelInfo common.ModelInfo) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
responseId := fmt.Sprintf(responseIDFormat, time.Now().Format("20060102150405"))
ctx := c.Request.Context()
cookieManager := config.NewCookieManager()
maxRetries := len(cookieManager.Cookies)
cookie, err := cookieManager.GetRandomCookie()
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
thinkStartType := new(bool)
thinkEndType := new(bool)
c.Stream(func(w io.Writer) bool {
for attempt := 0; attempt < maxRetries; attempt++ {
requestBody, err := createRequestBody(c, &openAIReq, modelInfo)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return false
}
jsonData, err := json.Marshal(requestBody)
if err != nil {
c.JSON(500, gin.H{"error": "Failed to marshal request body"})
return false
}
sseChan, err := kilo_api.MakeStreamChatRequest(c, client, jsonData, cookie, modelInfo)
if err != nil {
logger.Errorf(ctx, "MakeStreamChatRequest err on attempt %d: %v", attempt+1, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return false
}
isRateLimit := false
SSELoop:
for response := range sseChan {
if response.Status == 403 {
compressedData := []byte(response.Data)
compressedReader := bytes.NewReader(compressedData)
gzipReader, err := gzip.NewReader(compressedReader)
if err != nil {
logger.Errorf(c.Request.Context(), "Failed to create gzip reader: %v", err)
c.JSON(500, gin.H{"error": "Failed to create gzip reader"})
return false
}
// 读取解压后的数据
uncompressedData, err := io.ReadAll(gzipReader)
if err != nil {
logger.Errorf(c.Request.Context(), "Failed to read uncompressed data: %v", err)
c.JSON(500, gin.H{"error": "Failed to read uncompressed data"})
return false
}
gzipReader.Close()
logger.Errorf(c, string(uncompressedData))
//c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
config.RemoveCookie(cookie)
isRateLimit = true
break SSELoop
}
data := response.Data
if data == "" {
continue
}
if response.Done {
switch {
case common.IsUsageLimitExceeded(data):
if config.CheatEnabled {
split := strings.Split(cookie, "=")
if len(split) == 2 {
cookieSession := split[1]
cheatResp, err := client.Do(config.CheatUrl, cycletls.Options{
Timeout: 10 * 60 * 60,
Proxy: config.ProxyUrl, // 在每个请求中设置代理
Body: "",
Headers: map[string]string{
"Cookie": cookieSession,
},
}, "POST")
if err != nil {
logger.Errorf(ctx, "Cheat err Cookie: %s err: %v", cookie, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return false
}
if cheatResp.Status == 200 {
logger.Debug(c, fmt.Sprintf("Cheat Success Cookie: %s", cookie))
attempt-- // 抵消循环结束时的attempt++
break SSELoop
}
if cheatResp.Status == 402 {
logger.Warnf(ctx, "Cheat failed. Cookie: %s Resp: %v", cookie, cheatResp.Body)
} else {
logger.Errorf(ctx, "Cheat err Cookie: %s Resp: %v", cookie, cheatResp.Body)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Cheat Resp.Status:%v Resp.Body:%v", cheatResp.Status, cheatResp.Body)})
return false
}
}
}
isRateLimit = true
logger.Warnf(ctx, "Cookie Usage limit exceeded, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie)
config.RemoveCookie(cookie)
break SSELoop
case common.IsServerError(data):
logger.Errorf(ctx, errServerErrMsg)
c.JSON(http.StatusInternalServerError, gin.H{"error": errServerErrMsg})
return false
case common.IsNotLogin(data):
isRateLimit = true
logger.Warnf(ctx, "Cookie Not Login, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie)
break SSELoop // 使用 label 跳出 SSE 循环
case common.IsRateLimit(data):
isRateLimit = true
logger.Warnf(ctx, "Cookie rate limited, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie)
config.AddRateLimitCookie(cookie, time.Now().Add(time.Duration(config.RateLimitCookieLockDuration)*time.Second))
break SSELoop
}
logger.Warnf(ctx, response.Data)
return false
}
logger.Debug(ctx, strings.TrimSpace(data))
_, shouldContinue := processStreamData(c, data, responseId, openAIReq.Model, modelInfo, jsonData, thinkStartType, thinkEndType)
// 处理事件流数据
if !shouldContinue {
return false
}
}
if !isRateLimit {
return true
}
// 获取下一个可用的cookie继续尝试
cookie, err = cookieManager.GetNextCookie()
if err != nil {
logger.Errorf(ctx, "No more valid cookies available after attempt %d", attempt+1)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return false
}
}
logger.Errorf(ctx, "All cookies exhausted after %d attempts", maxRetries)
c.JSON(http.StatusInternalServerError, gin.H{"error": "All cookies are temporarily unavailable."})
return false
})
}
// 处理流式数据的辅助函数,返回bool表示是否继续处理
func processStreamData(c *gin.Context, data, responseId, model string, modelInfo common.ModelInfo, jsonData []byte, thinkStartType, thinkEndType *bool) (string, bool) {
data = strings.TrimSpace(data)
data = strings.TrimPrefix(data, "data: ")
// 处理[DONE]标记
if data == "[DONE]" {
return "", false
}
var event map[string]interface{}
if err := json.Unmarshal([]byte(data), &event); err != nil {
logger.Errorf(c.Request.Context(), "Failed to unmarshal event: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return "", false
}
if modelInfo.Source == "claude" {
eventType, ok := event["type"]
if !ok {
logger.Errorf(c.Request.Context(), "Event type not found")
return "", false
}
if eventType == "message_stop" {
handleMessageResult(c, responseId, model, jsonData)
return "", false
}
var text string
deltaMap, ok := event["delta"].(map[string]interface{})
if ok {
thinking, ok := deltaMap["thinking"].(string)
if ok {
if !*thinkStartType {
text = "\n\n" + thinking
*thinkStartType = true
*thinkEndType = false
} else {
text = thinking
}
}
deltaText, ok := deltaMap["text"].(string)
if ok {
if *thinkStartType && !*thinkEndType {
text = "\n\n" + deltaText
*thinkStartType = false
*thinkEndType = true
} else {
text = deltaText
}
}
if err := handleDelta(c, text, responseId, model, jsonData); err != nil {
logger.Errorf(c.Request.Context(), "handleDelta err: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return "", false
}
return text, true
}
return "", true
} else if modelInfo.Source == "openrouter" {
// 检查是否有choices数组
choices, ok := event["choices"].([]interface{})
if !ok || len(choices) == 0 {
// 如果有usage信息但没有内容,可能是最后一个消息
if _, hasUsage := event["usage"]; hasUsage {
return "", false
}
logger.Errorf(c.Request.Context(), "Invalid openrouter response format: choices not found or empty")
return "", false
}
// 获取第一个choice
choice, ok := choices[0].(map[string]interface{})
if !ok {
logger.Errorf(c.Request.Context(), "Invalid choice format in openrouter response")
return "", false
}
// 获取delta内容
delta, ok := choice["delta"].(map[string]interface{})
if !ok {
logger.Errorf(c.Request.Context(), "Delta not found in openrouter response")
return "", false
}
// 获取内容文本
content, ok := delta["content"].(string)
if !ok {
// 没有内容,可能是其他类型的更新
return "", true
}
// 处理文本内容
if err := handleDelta(c, content, responseId, model, jsonData); err != nil {
logger.Errorf(c.Request.Context(), "handleDelta err: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return "", false
}
// 检查是否完成 - 在处理内容后检查
finishReason, hasFinishReason := choice["finish_reason"]
if hasFinishReason && finishReason != nil && finishReason != "" {
// 处理完成的消息
handleMessageResult(c, responseId, model, jsonData)
return content, false // 返回内容但标记为结束
}
return content, true
}
return "", false
}
func processNoStreamData(c *gin.Context, data string, modelInfo common.ModelInfo, thinkStartType *bool, thinkEndType *bool) (string, bool) {
data = strings.TrimSpace(data)
data = strings.TrimPrefix(data, "data: ")
// 处理[DONE]标记
if data == "[DONE]" {
return "", false
}
var event map[string]interface{}
if err := json.Unmarshal([]byte(data), &event); err != nil {
logger.Errorf(c.Request.Context(), "Failed to unmarshal event: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return "", false
}
if modelInfo.Source == "claude" {
eventType, ok := event["type"]
if !ok {
logger.Errorf(c.Request.Context(), "Event type not found")
return "", false
}
if eventType == "message_stop" {
return "", false
}
var text string
deltaMap, ok := event["delta"].(map[string]interface{})
if ok {
thinking, ok := deltaMap["thinking"].(string)
if ok {
if !*thinkStartType {
text = "\n\n" + thinking
*thinkStartType = true
*thinkEndType = false
} else {
text = thinking
}
}
deltaText, ok := deltaMap["text"].(string)
if ok {
if *thinkStartType && !*thinkEndType {
text = "\n\n" + deltaText
*thinkStartType = false
*thinkEndType = true
} else {
text = deltaText
}
}
return text, true
}
return "", true
} else if modelInfo.Source == "openrouter" {
// 检查是否有choices数组
choices, ok := event["choices"].([]interface{})
if !ok || len(choices) == 0 {
// 如果有usage信息但没有内容,可能是最后一个消息
if _, hasUsage := event["usage"]; hasUsage {
return "", false
}
logger.Errorf(c.Request.Context(), "Invalid openrouter response format: choices not found or empty")
return "", false
}
// 获取第一个choice
choice, ok := choices[0].(map[string]interface{})
if !ok {
logger.Errorf(c.Request.Context(), "Invalid choice format in openrouter response")
return "", false
}
// 获取delta内容
delta, ok := choice["delta"].(map[string]interface{})
if !ok {
logger.Errorf(c.Request.Context(), "Delta not found in openrouter response")
return "", false
}
// 获取内容文本
content, ok := delta["content"].(string)
if !ok {
// 没有内容,可能是其他类型的更新
return "", true
}
// 检查是否完成 - 在处理内容后检查
finishReason, hasFinishReason := choice["finish_reason"]
if hasFinishReason && finishReason != nil && finishReason != "" {
// 处理完成的消息
return content, false // 返回内容但标记为结束
}
return content, true
}
return "", false
}
// OpenaiModels @Summary OpenAI模型列表接口
// @Description OpenAI模型列表接口
// @Tags OpenAI
// @Accept json
// @Produce json
// @Param Authorization header string true "Authorization API-KEY"
// @Success 200 {object} common.ResponseResult{data=model.OpenaiModelListResponse} "成功"
// @Router /v1/models [get]
func OpenaiModels(c *gin.Context) {
var modelsResp []string
modelsResp = lo.Union(common.GetModelList())
var openaiModelListResponse model.OpenaiModelListResponse
var openaiModelResponse []model.OpenaiModelResponse
openaiModelListResponse.Object = "list"
for _, modelResp := range modelsResp {
openaiModelResponse = append(openaiModelResponse, model.OpenaiModelResponse{
ID: modelResp,
Object: "model",
})
}
openaiModelListResponse.Data = openaiModelResponse
c.JSON(http.StatusOK, openaiModelListResponse)
return
}
func safeClose(client cycletls.CycleTLS) {
if client.ReqChan != nil {
close(client.ReqChan)
}
if client.RespChan != nil {
close(client.RespChan)
}
}
//
//func processUrl(c *gin.Context, client cycletls.CycleTLS, chatId, cookie string, url string) (string, error) {
// // 判断是否为URL
// if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
// // 下载文件
// bytes, err := fetchImageBytes(url)
// if err != nil {
// logger.Errorf(c.Request.Context(), fmt.Sprintf("fetchImageBytes err %v\n", err))
// return "", fmt.Errorf("fetchImageBytes err %v\n", err)
// }
//
// base64Str := base64.StdEncoding.EncodeToString(bytes)
//
// finalUrl, err := processBytes(c, client, chatId, cookie, base64Str)
// if err != nil {
// logger.Errorf(c.Request.Context(), fmt.Sprintf("processBytes err %v\n", err))
// return "", fmt.Errorf("processBytes err %v\n", err)
// }
// return finalUrl, nil
// } else {
// finalUrl, err := processBytes(c, client, chatId, cookie, url)
// if err != nil {
// logger.Errorf(c.Request.Context(), fmt.Sprintf("processBytes err %v\n", err))
// return "", fmt.Errorf("processBytes err %v\n", err)
// }
// return finalUrl, nil
// }
//}
//
//func fetchImageBytes(url string) ([]byte, error) {
// resp, err := http.Get(url)
// if err != nil {
// return nil, fmt.Errorf("http.Get err: %v\n", err)
// }
// defer resp.Body.Close()
//
// return io.ReadAll(resp.Body)
//}
//
//func processBytes(c *gin.Context, client cycletls.CycleTLS, chatId, cookie string, base64Str string) (string, error) {
// // 检查类型
// fileType := common.DetectFileType(base64Str)
// if !fileType.IsValid {
// return "", fmt.Errorf("invalid file type %s", fileType.Extension)
// }
// signUrl, err := kilo-api.GetSignURL(client, cookie, chatId, fileType.Extension)
// if err != nil {
// logger.Errorf(c.Request.Context(), fmt.Sprintf("GetSignURL err %v\n", err))
// return "", fmt.Errorf("GetSignURL err: %v\n", err)
// }
//
// err = kilo-api.UploadToS3(client, signUrl, base64Str, fileType.MimeType)
// if err != nil {
// logger.Errorf(c.Request.Context(), fmt.Sprintf("UploadToS3 err %v\n", err))
// return "", err
// }
//
// u, err := url.Parse(signUrl)
// if err != nil {
// return "", err
// }
//
// return fmt.Sprintf("%s://%s%s", u.Scheme, u.Host, u.Path), nil
//}