mirror of
				https://github.com/songquanpeng/one-api.git
				synced 2025-10-31 22:03:41 +08:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			v0.5.10-al
			...
			v0.5.11-al
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 1c8922153d | ||
|  | f3c07e1451 | ||
|  | 40ceb29e54 | ||
|  | 0699ecd0af | ||
|  | ee9e746520 | ||
|  | a763681c2e | ||
|  | b7fcb319da | 
| @@ -1,6 +1,8 @@ | ||||
| package image | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/base64" | ||||
| 	"image" | ||||
| 	_ "image/gif" | ||||
| 	_ "image/jpeg" | ||||
| @@ -8,11 +10,27 @@ import ( | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	_ "golang.org/x/image/webp" | ||||
| ) | ||||
|  | ||||
| func IsImageUrl(url string) (bool, error) { | ||||
| 	resp, err := http.Head(url) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	if !strings.HasPrefix(resp.Header.Get("Content-Type"), "image/") { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 	return true, nil | ||||
| } | ||||
|  | ||||
| func GetImageSizeFromUrl(url string) (width int, height int, err error) { | ||||
| 	isImage, err := IsImageUrl(url) | ||||
| 	if !isImage { | ||||
| 		return | ||||
| 	} | ||||
| 	resp, err := http.Get(url) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| @@ -25,17 +43,51 @@ func GetImageSizeFromUrl(url string) (width int, height int, err error) { | ||||
| 	return img.Width, img.Height, nil | ||||
| } | ||||
|  | ||||
| func GetImageFromUrl(url string) (mimeType string, data string, err error) { | ||||
| 	isImage, err := IsImageUrl(url) | ||||
| 	if !isImage { | ||||
| 		return | ||||
| 	} | ||||
| 	resp, err := http.Get(url) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 	buffer := bytes.NewBuffer(nil) | ||||
| 	_, err = buffer.ReadFrom(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	mimeType = resp.Header.Get("Content-Type") | ||||
| 	data = base64.StdEncoding.EncodeToString(buffer.Bytes()) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	reg = regexp.MustCompile(`data:image/([^;]+);base64,`) | ||||
| ) | ||||
|  | ||||
| var readerPool = sync.Pool{ | ||||
| 	New: func() interface{} { | ||||
| 		return &bytes.Reader{} | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func GetImageSizeFromBase64(encoded string) (width int, height int, err error) { | ||||
| 	encoded = strings.TrimPrefix(encoded, "data:image/png;base64,") | ||||
| 	base64 := strings.NewReader(reg.ReplaceAllString(encoded, "")) | ||||
| 	img, _, err := image.DecodeConfig(base64) | ||||
| 	decoded, err := base64.StdEncoding.DecodeString(reg.ReplaceAllString(encoded, "")) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 		return 0, 0, err | ||||
| 	} | ||||
|  | ||||
| 	reader := readerPool.Get().(*bytes.Reader) | ||||
| 	defer readerPool.Put(reader) | ||||
| 	reader.Reset(decoded) | ||||
|  | ||||
| 	img, _, err := image.DecodeConfig(reader) | ||||
| 	if err != nil { | ||||
| 		return 0, 0, err | ||||
| 	} | ||||
|  | ||||
| 	return img.Width, img.Height, nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -152,3 +152,20 @@ func TestGetImageSize(t *testing.T) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGetImageSizeFromBase64(t *testing.T) { | ||||
| 	for i, c := range cases { | ||||
| 		t.Run("Decode:"+strconv.Itoa(i), func(t *testing.T) { | ||||
| 			resp, err := http.Get(c.url) | ||||
| 			assert.NoError(t, err) | ||||
| 			defer resp.Body.Close() | ||||
| 			data, err := io.ReadAll(resp.Body) | ||||
| 			assert.NoError(t, err) | ||||
| 			encoded := base64.StdEncoding.EncodeToString(data) | ||||
| 			width, height, err := img.GetImageSizeFromBase64(encoded) | ||||
| 			assert.NoError(t, err) | ||||
| 			assert.Equal(t, c.width, width) | ||||
| 			assert.Equal(t, c.height, height) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -36,7 +36,11 @@ func init() { | ||||
| 	} | ||||
|  | ||||
| 	if os.Getenv("SESSION_SECRET") != "" { | ||||
| 		SessionSecret = os.Getenv("SESSION_SECRET") | ||||
| 		if os.Getenv("SESSION_SECRET") == "random_string" { | ||||
| 			SysError("SESSION_SECRET is set to an example value, please change it to a random string.") | ||||
| 		} else { | ||||
| 			SessionSecret = os.Getenv("SESSION_SECRET") | ||||
| 		} | ||||
| 	} | ||||
| 	if os.Getenv("SQLITE_PATH") != "" { | ||||
| 		SQLitePath = os.Getenv("SQLITE_PATH") | ||||
|   | ||||
| @@ -84,6 +84,7 @@ var ModelRatio = map[string]float64{ | ||||
| 	"Embedding-V1":              0.1429, // ¥0.002 / 1k tokens | ||||
| 	"PaLM-2":                    1, | ||||
| 	"gemini-pro":                1,      // $0.00025 / 1k characters -> $0.001 / 1k tokens | ||||
| 	"gemini-pro-vision":         1,      // $0.00025 / 1k characters -> $0.001 / 1k tokens | ||||
| 	"chatglm_turbo":             0.3572, // ¥0.005 / 1k tokens | ||||
| 	"chatglm_pro":               0.7143, // ¥0.01 / 1k tokens | ||||
| 	"chatglm_std":               0.3572, // ¥0.005 / 1k tokens | ||||
| @@ -115,6 +116,9 @@ func UpdateModelRatioByJSONString(jsonStr string) error { | ||||
| } | ||||
|  | ||||
| func GetModelRatio(name string) float64 { | ||||
| 	if strings.HasPrefix(name, "qwen-") && strings.HasSuffix(name, "-internet") { | ||||
| 		name = strings.TrimSuffix(name, "-internet") | ||||
| 	} | ||||
| 	ratio, ok := ModelRatio[name] | ||||
| 	if !ok { | ||||
| 		SysError("model ratio not found: " + name) | ||||
|   | ||||
| @@ -432,6 +432,15 @@ func init() { | ||||
| 			Root:       "gemini-pro", | ||||
| 			Parent:     nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Id:         "gemini-pro-vision", | ||||
| 			Object:     "model", | ||||
| 			Created:    1677649963, | ||||
| 			OwnedBy:    "google", | ||||
| 			Permission: permission, | ||||
| 			Root:       "gemini-pro-vision", | ||||
| 			Parent:     nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Id:         "chatglm_turbo", | ||||
| 			Object:     "model", | ||||
|   | ||||
| @@ -23,10 +23,11 @@ type AliInput struct { | ||||
| } | ||||
|  | ||||
| type AliParameters struct { | ||||
| 	TopP         float64 `json:"top_p,omitempty"` | ||||
| 	TopK         int     `json:"top_k,omitempty"` | ||||
| 	Seed         uint64  `json:"seed,omitempty"` | ||||
| 	EnableSearch bool    `json:"enable_search,omitempty"` | ||||
| 	TopP              float64 `json:"top_p,omitempty"` | ||||
| 	TopK              int     `json:"top_k,omitempty"` | ||||
| 	Seed              uint64  `json:"seed,omitempty"` | ||||
| 	EnableSearch      bool    `json:"enable_search,omitempty"` | ||||
| 	IncrementalOutput bool    `json:"incremental_output,omitempty"` | ||||
| } | ||||
|  | ||||
| type AliChatRequest struct { | ||||
| @@ -81,6 +82,8 @@ type AliChatResponse struct { | ||||
| 	AliError | ||||
| } | ||||
|  | ||||
| const AliEnableSearchModelSuffix = "-internet" | ||||
|  | ||||
| func requestOpenAI2Ali(request GeneralOpenAIRequest) *AliChatRequest { | ||||
| 	messages := make([]AliMessage, 0, len(request.Messages)) | ||||
| 	for i := 0; i < len(request.Messages); i++ { | ||||
| @@ -90,17 +93,21 @@ func requestOpenAI2Ali(request GeneralOpenAIRequest) *AliChatRequest { | ||||
| 			Role:    strings.ToLower(message.Role), | ||||
| 		}) | ||||
| 	} | ||||
| 	enableSearch := false | ||||
| 	aliModel := request.Model | ||||
| 	if strings.HasSuffix(aliModel, AliEnableSearchModelSuffix) { | ||||
| 		enableSearch = true | ||||
| 		aliModel = strings.TrimSuffix(aliModel, AliEnableSearchModelSuffix) | ||||
| 	} | ||||
| 	return &AliChatRequest{ | ||||
| 		Model: request.Model, | ||||
| 		Model: aliModel, | ||||
| 		Input: AliInput{ | ||||
| 			Messages: messages, | ||||
| 		}, | ||||
| 		//Parameters: AliParameters{  // ChatGPT's parameters are not compatible with Ali's | ||||
| 		//	TopP: request.TopP, | ||||
| 		//	TopK: 50, | ||||
| 		//	//Seed:         0, | ||||
| 		//	//EnableSearch: false, | ||||
| 		//}, | ||||
| 		Parameters: AliParameters{ | ||||
| 			EnableSearch:      enableSearch, | ||||
| 			IncrementalOutput: request.Stream, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -202,7 +209,7 @@ func streamResponseAli2OpenAI(aliResponse *AliChatResponse) *ChatCompletionsStre | ||||
| 		Id:      aliResponse.RequestId, | ||||
| 		Object:  "chat.completion.chunk", | ||||
| 		Created: common.GetTimestamp(), | ||||
| 		Model:   "ernie-bot", | ||||
| 		Model:   "qwen", | ||||
| 		Choices: []ChatCompletionsStreamResponseChoice{choice}, | ||||
| 	} | ||||
| 	return &response | ||||
| @@ -240,7 +247,7 @@ func aliStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStat | ||||
| 		stopChan <- true | ||||
| 	}() | ||||
| 	setEventStreamHeaders(c) | ||||
| 	lastResponseText := "" | ||||
| 	//lastResponseText := "" | ||||
| 	c.Stream(func(w io.Writer) bool { | ||||
| 		select { | ||||
| 		case data := <-dataChan: | ||||
| @@ -256,8 +263,8 @@ func aliStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStat | ||||
| 				usage.TotalTokens = aliResponse.Usage.InputTokens + aliResponse.Usage.OutputTokens | ||||
| 			} | ||||
| 			response := streamResponseAli2OpenAI(&aliResponse) | ||||
| 			response.Choices[0].Delta.Content = strings.TrimPrefix(response.Choices[0].Delta.Content, lastResponseText) | ||||
| 			lastResponseText = aliResponse.Output.Text | ||||
| 			//response.Choices[0].Delta.Content = strings.TrimPrefix(response.Choices[0].Delta.Content, lastResponseText) | ||||
| 			//lastResponseText = aliResponse.Output.Text | ||||
| 			jsonResponse, err := json.Marshal(response) | ||||
| 			if err != nil { | ||||
| 				common.SysError("error marshalling stream response: " + err.Error()) | ||||
| @@ -303,6 +310,7 @@ func aliHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode | ||||
| 		}, nil | ||||
| 	} | ||||
| 	fullTextResponse := responseAli2OpenAI(&aliResponse) | ||||
| 	fullTextResponse.Model = "qwen" | ||||
| 	jsonResponse, err := json.Marshal(fullTextResponse) | ||||
| 	if err != nil { | ||||
| 		return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil | ||||
|   | ||||
| @@ -255,6 +255,7 @@ func baiduHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCo | ||||
| 		}, nil | ||||
| 	} | ||||
| 	fullTextResponse := responseBaidu2OpenAI(&baiduResponse) | ||||
| 	fullTextResponse.Model = "ernie-bot" | ||||
| 	jsonResponse, err := json.Marshal(fullTextResponse) | ||||
| 	if err != nil { | ||||
| 		return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil | ||||
|   | ||||
| @@ -204,6 +204,7 @@ func claudeHandler(c *gin.Context, resp *http.Response, promptTokens int, model | ||||
| 		}, nil | ||||
| 	} | ||||
| 	fullTextResponse := responseClaude2OpenAI(&claudeResponse) | ||||
| 	fullTextResponse.Model = model | ||||
| 	completionTokens := countTokenText(claudeResponse.Completion, model) | ||||
| 	usage := Usage{ | ||||
| 		PromptTokens:     promptTokens, | ||||
|   | ||||
| @@ -7,11 +7,18 @@ import ( | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"one-api/common" | ||||
| 	"one-api/common/image" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
|  | ||||
| // https://ai.google.dev/docs/gemini_api_overview?hl=zh-cn | ||||
|  | ||||
| const ( | ||||
| 	GeminiVisionMaxImageNum = 16 | ||||
| ) | ||||
|  | ||||
| type GeminiChatRequest struct { | ||||
| 	Contents         []GeminiChatContent        `json:"contents"` | ||||
| 	SafetySettings   []GeminiChatSafetySettings `json:"safety_settings,omitempty"` | ||||
| @@ -97,6 +104,30 @@ func requestOpenAI2Gemini(textRequest GeneralOpenAIRequest) *GeminiChatRequest { | ||||
| 				}, | ||||
| 			}, | ||||
| 		} | ||||
| 		openaiContent := message.ParseContent() | ||||
| 		var parts []GeminiPart | ||||
| 		imageNum := 0 | ||||
| 		for _, part := range openaiContent { | ||||
| 			if part.Type == ContentTypeText { | ||||
| 				parts = append(parts, GeminiPart{ | ||||
| 					Text: part.Text, | ||||
| 				}) | ||||
| 			} else if part.Type == ContentTypeImageURL { | ||||
| 				imageNum += 1 | ||||
| 				if imageNum > GeminiVisionMaxImageNum { | ||||
| 					continue | ||||
| 				} | ||||
| 				mimeType, data, _ := image.GetImageFromUrl(part.ImageURL.Url) | ||||
| 				parts = append(parts, GeminiPart{ | ||||
| 					InlineData: &GeminiInlineData{ | ||||
| 						MimeType: mimeType, | ||||
| 						Data:     data, | ||||
| 					}, | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 		content.Parts = parts | ||||
|  | ||||
| 		// there's no assistant role in gemini and API shall vomit if Role is not user or model | ||||
| 		if content.Role == "assistant" { | ||||
| 			content.Role = "model" | ||||
| @@ -287,6 +318,7 @@ func geminiChatHandler(c *gin.Context, resp *http.Response, promptTokens int, mo | ||||
| 		}, nil | ||||
| 	} | ||||
| 	fullTextResponse := responseGeminiChat2OpenAI(&geminiResponse) | ||||
| 	fullTextResponse.Model = model | ||||
| 	completionTokens := countTokenText(geminiResponse.GetResponseText(), model) | ||||
| 	usage := Usage{ | ||||
| 		PromptTokens:     promptTokens, | ||||
|   | ||||
| @@ -187,6 +187,7 @@ func palmHandler(c *gin.Context, resp *http.Response, promptTokens int, model st | ||||
| 		}, nil | ||||
| 	} | ||||
| 	fullTextResponse := responsePaLM2OpenAI(&palmResponse) | ||||
| 	fullTextResponse.Model = model | ||||
| 	completionTokens := countTokenText(palmResponse.Candidates[0].Content, model) | ||||
| 	usage := Usage{ | ||||
| 		PromptTokens:     promptTokens, | ||||
|   | ||||
| @@ -237,6 +237,7 @@ func tencentHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatus | ||||
| 		}, nil | ||||
| 	} | ||||
| 	fullTextResponse := responseTencent2OpenAI(&TencentResponse) | ||||
| 	fullTextResponse.Model = "hunyuan" | ||||
| 	jsonResponse, err := json.Marshal(fullTextResponse) | ||||
| 	if err != nil { | ||||
| 		return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil | ||||
|   | ||||
| @@ -180,9 +180,6 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | ||||
| 		if baseURL != "" { | ||||
| 			fullRequestURL = fmt.Sprintf("%s/v1beta2/models/chat-bison-001:generateMessage", baseURL) | ||||
| 		} | ||||
| 		apiKey := c.Request.Header.Get("Authorization") | ||||
| 		apiKey = strings.TrimPrefix(apiKey, "Bearer ") | ||||
| 		fullRequestURL += "?key=" + apiKey | ||||
| 	case APITypeGemini: | ||||
| 		requestBaseURL := "https://generativelanguage.googleapis.com" | ||||
| 		if baseURL != "" { | ||||
| @@ -197,9 +194,6 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | ||||
| 			action = "streamGenerateContent" | ||||
| 		} | ||||
| 		fullRequestURL = fmt.Sprintf("%s/%s/models/%s:%s", requestBaseURL, version, textRequest.Model, action) | ||||
| 		apiKey := c.Request.Header.Get("Authorization") | ||||
| 		apiKey = strings.TrimPrefix(apiKey, "Bearer ") | ||||
| 		fullRequestURL += "?key=" + apiKey | ||||
| 	case APITypeZhipu: | ||||
| 		method := "invoke" | ||||
| 		if textRequest.Stream { | ||||
| @@ -396,9 +390,9 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | ||||
| 		case APITypeTencent: | ||||
| 			req.Header.Set("Authorization", apiKey) | ||||
| 		case APITypePaLM: | ||||
| 			// do not set Authorization header | ||||
| 			req.Header.Set("x-goog-api-key", apiKey) | ||||
| 		case APITypeGemini: | ||||
| 			// do not set Authorization header | ||||
| 			req.Header.Set("x-goog-api-key", apiKey) | ||||
| 		default: | ||||
| 			req.Header.Set("Authorization", "Bearer "+apiKey) | ||||
| 		} | ||||
|   | ||||
| @@ -290,6 +290,7 @@ func zhipuHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCo | ||||
| 		}, nil | ||||
| 	} | ||||
| 	fullTextResponse := responseZhipu2OpenAI(&zhipuResponse) | ||||
| 	fullTextResponse.Model = "chatglm" | ||||
| 	jsonResponse, err := json.Marshal(fullTextResponse) | ||||
| 	if err != nil { | ||||
| 		return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil | ||||
|   | ||||
| @@ -31,6 +31,22 @@ type ImageContent struct { | ||||
| 	ImageURL *ImageURL `json:"image_url,omitempty"` | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	ContentTypeText     = "text" | ||||
| 	ContentTypeImageURL = "image_url" | ||||
| ) | ||||
|  | ||||
| type OpenAIMessageContent struct { | ||||
| 	Type     string    `json:"type,omitempty"` | ||||
| 	Text     string    `json:"text"` | ||||
| 	ImageURL *ImageURL `json:"image_url,omitempty"` | ||||
| } | ||||
|  | ||||
| func (m Message) IsStringContent() bool { | ||||
| 	_, ok := m.Content.(string) | ||||
| 	return ok | ||||
| } | ||||
|  | ||||
| func (m Message) StringContent() string { | ||||
| 	content, ok := m.Content.(string) | ||||
| 	if ok { | ||||
| @@ -44,7 +60,7 @@ func (m Message) StringContent() string { | ||||
| 			if !ok { | ||||
| 				continue | ||||
| 			} | ||||
| 			if contentMap["type"] == "text" { | ||||
| 			if contentMap["type"] == ContentTypeText { | ||||
| 				if subStr, ok := contentMap["text"].(string); ok { | ||||
| 					contentStr += subStr | ||||
| 				} | ||||
| @@ -55,6 +71,47 @@ func (m Message) StringContent() string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m Message) ParseContent() []OpenAIMessageContent { | ||||
| 	var contentList []OpenAIMessageContent | ||||
| 	content, ok := m.Content.(string) | ||||
| 	if ok { | ||||
| 		contentList = append(contentList, OpenAIMessageContent{ | ||||
| 			Type: ContentTypeText, | ||||
| 			Text: content, | ||||
| 		}) | ||||
| 		return contentList | ||||
| 	} | ||||
| 	anyList, ok := m.Content.([]any) | ||||
| 	if ok { | ||||
| 		for _, contentItem := range anyList { | ||||
| 			contentMap, ok := contentItem.(map[string]any) | ||||
| 			if !ok { | ||||
| 				continue | ||||
| 			} | ||||
| 			switch contentMap["type"] { | ||||
| 			case ContentTypeText: | ||||
| 				if subStr, ok := contentMap["text"].(string); ok { | ||||
| 					contentList = append(contentList, OpenAIMessageContent{ | ||||
| 						Type: ContentTypeText, | ||||
| 						Text: subStr, | ||||
| 					}) | ||||
| 				} | ||||
| 			case ContentTypeImageURL: | ||||
| 				if subObj, ok := contentMap["image_url"].(map[string]any); ok { | ||||
| 					contentList = append(contentList, OpenAIMessageContent{ | ||||
| 						Type: ContentTypeImageURL, | ||||
| 						ImageURL: &ImageURL{ | ||||
| 							Url: subObj["url"].(string), | ||||
| 						}, | ||||
| 					}) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return contentList | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	RelayModeUnknown = iota | ||||
| 	RelayModeChatCompletions | ||||
| @@ -206,6 +263,7 @@ type OpenAITextResponseChoice struct { | ||||
|  | ||||
| type OpenAITextResponse struct { | ||||
| 	Id      string                     `json:"id"` | ||||
| 	Model   string                     `json:"model,omitempty"` | ||||
| 	Object  string                     `json:"object"` | ||||
| 	Created int64                      `json:"created"` | ||||
| 	Choices []OpenAITextResponseChoice `json:"choices"` | ||||
|   | ||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @@ -16,7 +16,7 @@ require ( | ||||
| 	github.com/gorilla/websocket v1.5.0 | ||||
| 	github.com/pkoukk/tiktoken-go v0.1.5 | ||||
| 	github.com/stretchr/testify v1.8.3 | ||||
| 	golang.org/x/crypto v0.14.0 | ||||
| 	golang.org/x/crypto v0.17.0 | ||||
| 	golang.org/x/image v0.14.0 | ||||
| 	gorm.io/driver/mysql v1.4.3 | ||||
| 	gorm.io/driver/postgres v1.5.2 | ||||
| @@ -58,7 +58,7 @@ require ( | ||||
| 	github.com/ugorji/go/codec v1.2.11 // indirect | ||||
| 	golang.org/x/arch v0.3.0 // indirect | ||||
| 	golang.org/x/net v0.17.0 // indirect | ||||
| 	golang.org/x/sys v0.13.0 // indirect | ||||
| 	golang.org/x/sys v0.15.0 // indirect | ||||
| 	golang.org/x/text v0.14.0 // indirect | ||||
| 	google.golang.org/protobuf v1.30.0 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||
|   | ||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							| @@ -150,8 +150,8 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu | ||||
| golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= | ||||
| golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | ||||
| golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= | ||||
| golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= | ||||
| golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= | ||||
| golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= | ||||
| golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= | ||||
| golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| @@ -164,8 +164,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc | ||||
| golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= | ||||
| golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= | ||||
| golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"net/http" | ||||
| 	"one-api/common" | ||||
| 	"runtime/debug" | ||||
| ) | ||||
|  | ||||
| func RelayPanicRecover() gin.HandlerFunc { | ||||
| @@ -12,6 +13,7 @@ func RelayPanicRecover() gin.HandlerFunc { | ||||
| 		defer func() { | ||||
| 			if err := recover(); err != nil { | ||||
| 				common.SysError(fmt.Sprintf("panic detected: %v", err)) | ||||
| 				common.SysError(fmt.Sprintf("stacktrace from panic: %s", string(debug.Stack()))) | ||||
| 				c.JSON(http.StatusInternalServerError, gin.H{ | ||||
| 					"error": gin.H{ | ||||
| 						"message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://github.com/songquanpeng/one-api", err), | ||||
|   | ||||
| @@ -42,7 +42,11 @@ func GetAllUsers(startIdx int, num int) (users []*User, err error) { | ||||
| } | ||||
|  | ||||
| func SearchUsers(keyword string) (users []*User, err error) { | ||||
| 	err = DB.Omit("password").Where("id = ? or username LIKE ? or email LIKE ? or display_name LIKE ?", keyword, keyword+"%", keyword+"%", keyword+"%").Find(&users).Error | ||||
| 	if !common.UsingPostgreSQL { | ||||
| 		err = DB.Omit("password").Where("id = ? or username LIKE ? or email LIKE ? or display_name LIKE ?", keyword, keyword+"%", keyword+"%", keyword+"%").Find(&users).Error | ||||
| 	} else { | ||||
| 		err = DB.Omit("password").Where("username LIKE ? or email LIKE ? or display_name LIKE ?", keyword+"%", keyword+"%", keyword+"%").Find(&users).Error | ||||
| 	} | ||||
| 	return users, err | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -70,6 +70,13 @@ const EditChannel = () => { | ||||
|           break; | ||||
|         case 17: | ||||
|           localModels = ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-max-longcontext', 'text-embedding-v1']; | ||||
|           let withInternetVersion = []; | ||||
|           for (let i = 0; i < localModels.length; i++) { | ||||
|             if (localModels[i].startsWith('qwen-')) { | ||||
|               withInternetVersion.push(localModels[i] + '-internet'); | ||||
|             } | ||||
|           } | ||||
|           localModels = [...localModels, ...withInternetVersion]; | ||||
|           break; | ||||
|         case 16: | ||||
|           localModels = ['chatglm_turbo', 'chatglm_pro', 'chatglm_std', 'chatglm_lite']; | ||||
| @@ -84,7 +91,7 @@ const EditChannel = () => { | ||||
|           localModels = ['hunyuan']; | ||||
|           break; | ||||
|         case 24: | ||||
|           localModels = ['gemini-pro']; | ||||
|           localModels = ['gemini-pro', 'gemini-pro-vision']; | ||||
|           break; | ||||
|       } | ||||
|       setInputs((inputs) => ({ ...inputs, models: localModels })); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user