Compare commits

..

25 Commits

Author SHA1 Message Date
Calcium-Ion
8efa12b941 Merge pull request #953 from wkxu/main
fix: .env文件配置DEBUG=true等参数不起作用的fix
2025-04-10 16:14:11 +08:00
Calcium-Ion
7b997b3a2c Merge pull request #956 from HynoR/feat/xai
feat: add xAI channel
2025-04-10 16:13:48 +08:00
HynoR
700c05b826 feat: update adaptor methods and add new image model 2025-04-10 15:08:12 +08:00
HynoR
c5103237b0 feat: add xai grok-3-mini reasoning effort 2025-04-10 13:31:43 +08:00
HynoR
f500eb17a8 feat: add xai channel
feat: add xai channel

feat: add xai channel
2025-04-10 13:04:43 +08:00
wkxu
86f6bb7abe refactor: 把common/instants.go里的从Getenv获取的参数,放到init.go的LoadEnv函数里获取
把constant/env.go里的从Getenv获取的参数,放到env.go的InitEnv函数里获取。以避免.env文件配置参数不起作用的情况
2025-04-10 09:02:19 +08:00
Calcium-Ion
c4c1099ae5 Merge pull request #944 from lamcodes/main
Update: Gemini channel fetch_models
2025-04-10 00:09:54 +08:00
CaIon
c869455456 fix: Update model ratios for gemini-2.5-pro 2025-04-10 00:09:11 +08:00
CaIon
f89d8a0fe5 refactor: Remove duplicate model settings initialization in main function 2025-04-10 00:07:34 +08:00
CaIon
3d6d19903b refactor: Update localization keys for API address in English translations and adjust related UI labels 2025-04-09 22:22:19 +08:00
zkp
524d4a65bf Update: Gemini channel fetch_models 2025-04-08 22:43:13 +08:00
CaIon
082218173a feat: Add CheckSetup function call in main to ensure proper initialization #942 2025-04-08 18:14:36 +08:00
Calcium-Ion
67cbbc2266 Merge pull request #930 from Yiffyi/main
fix: save OIDC settings
2025-04-08 17:39:42 +08:00
CaIon
79b35e385f Update MaxTokens for gemini model to 300 in test request 2025-04-08 17:37:25 +08:00
Calcium-Ion
03e8ab4126 Merge pull request #936 from lamcodes/main
fix: gemini test MaxTokens
2025-04-08 17:33:31 +08:00
Calcium-Ion
30f32c6a6d Set MaxTokens to 50 for gemini 2025-04-08 17:33:10 +08:00
CaIon
5813ca780f feat: Integrate SetupCheck component for improved setup validation in routing 2025-04-08 17:31:46 +08:00
CaIon
aa34c3035a feat: Initialize model settings and improve concurrency control in operation settings 2025-04-07 22:20:47 +08:00
CaIon
fb9f595044 feat: Add concurrency control to group ratio management with mutexes 2025-04-07 21:55:54 +08:00
zkp
f24de65626 fix: gemini test MaxTokens 2025-04-06 23:24:47 +08:00
Yiffyi Jia
e34dccbc65 fix: cannot save OIDC settings 2025-04-05 04:24:38 +00:00
CaIon
f6e8887482 Update model-ratio.go 2025-04-04 23:43:14 +08:00
CaIon
a29f4d88c5 Update model-ratio.go 2025-04-04 23:41:41 +08:00
CaIon
a6bb30af41 fix: Improve setup check logic and logging for system initialization 2025-04-04 21:27:24 +08:00
CaIon
424424c160 Update model-ratio.go 2025-04-04 00:31:24 +08:00
23 changed files with 401 additions and 142 deletions

View File

@@ -1,8 +1,8 @@
package common
import (
"os"
"strconv"
//"os"
//"strconv"
"sync"
"time"
@@ -63,8 +63,8 @@ var EmailDomainWhitelist = []string{
"foxmail.com",
}
var DebugEnabled = os.Getenv("DEBUG") == "true"
var MemoryCacheEnabled = os.Getenv("MEMORY_CACHE_ENABLED") == "true"
var DebugEnabled bool
var MemoryCacheEnabled bool
var LogConsumeEnabled = true
@@ -103,22 +103,22 @@ var RetryTimes = 0
//var RootUserEmail = ""
var IsMasterNode = os.Getenv("NODE_TYPE") != "slave"
var IsMasterNode bool
var requestInterval, _ = strconv.Atoi(os.Getenv("POLLING_INTERVAL"))
var RequestInterval = time.Duration(requestInterval) * time.Second
var requestInterval int
var RequestInterval time.Duration
var SyncFrequency = GetEnvOrDefault("SYNC_FREQUENCY", 60) // unit is second
var SyncFrequency int // unit is second
var BatchUpdateEnabled = false
var BatchUpdateInterval = GetEnvOrDefault("BATCH_UPDATE_INTERVAL", 5)
var BatchUpdateInterval int
var RelayTimeout = GetEnvOrDefault("RELAY_TIMEOUT", 0) // unit is second
var RelayTimeout int // unit is second
var GeminiSafetySetting = GetEnvOrDefaultString("GEMINI_SAFETY_SETTING", "BLOCK_NONE")
var GeminiSafetySetting string
// https://docs.cohere.com/docs/safety-modes Type; NONE/CONTEXTUAL/STRICT
var CohereSafetySetting = GetEnvOrDefaultString("COHERE_SAFETY_SETTING", "NONE")
var CohereSafetySetting string
const (
RequestIdKey = "X-Oneapi-Request-Id"
@@ -145,13 +145,13 @@ var (
// All duration's unit is seconds
// Shouldn't larger then RateLimitKeyExpirationDuration
var (
GlobalApiRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_API_RATE_LIMIT_ENABLE", true)
GlobalApiRateLimitNum = GetEnvOrDefault("GLOBAL_API_RATE_LIMIT", 180)
GlobalApiRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_API_RATE_LIMIT_DURATION", 180))
GlobalApiRateLimitEnable bool
GlobalApiRateLimitNum int
GlobalApiRateLimitDuration int64
GlobalWebRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_WEB_RATE_LIMIT_ENABLE", true)
GlobalWebRateLimitNum = GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT", 60)
GlobalWebRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT_DURATION", 180))
GlobalWebRateLimitEnable bool
GlobalWebRateLimitNum int
GlobalWebRateLimitDuration int64
UploadRateLimitNum = 10
UploadRateLimitDuration int64 = 60
@@ -235,6 +235,7 @@ const (
ChannelTypeVolcEngine = 45
ChannelTypeBaiduV2 = 46
ChannelTypeXinference = 47
ChannelTypeXai = 48
ChannelTypeDummy // this one is only for count, do not add any channel after this
)
@@ -288,4 +289,5 @@ var ChannelBaseURLs = []string{
"https://ark.cn-beijing.volces.com", //45
"https://qianfan.baidubce.com", //46
"", //47
"https://api.x.ai", //48
}

View File

@@ -6,6 +6,8 @@ import (
"log"
"os"
"path/filepath"
"strconv"
"time"
)
var (
@@ -66,4 +68,31 @@ func LoadEnv() {
}
}
}
// Initialize variables from constants.go that were using environment variables
DebugEnabled = os.Getenv("DEBUG") == "true"
MemoryCacheEnabled = os.Getenv("MEMORY_CACHE_ENABLED") == "true"
IsMasterNode = os.Getenv("NODE_TYPE") != "slave"
// Parse requestInterval and set RequestInterval
requestInterval, _ = strconv.Atoi(os.Getenv("POLLING_INTERVAL"))
RequestInterval = time.Duration(requestInterval) * time.Second
// Initialize variables with GetEnvOrDefault
SyncFrequency = GetEnvOrDefault("SYNC_FREQUENCY", 60)
BatchUpdateInterval = GetEnvOrDefault("BATCH_UPDATE_INTERVAL", 5)
RelayTimeout = GetEnvOrDefault("RELAY_TIMEOUT", 0)
// Initialize string variables with GetEnvOrDefaultString
GeminiSafetySetting = GetEnvOrDefaultString("GEMINI_SAFETY_SETTING", "BLOCK_NONE")
CohereSafetySetting = GetEnvOrDefaultString("COHERE_SAFETY_SETTING", "NONE")
// Initialize rate limit variables
GlobalApiRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_API_RATE_LIMIT_ENABLE", true)
GlobalApiRateLimitNum = GetEnvOrDefault("GLOBAL_API_RATE_LIMIT", 180)
GlobalApiRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_API_RATE_LIMIT_DURATION", 180))
GlobalWebRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_WEB_RATE_LIMIT_ENABLE", true)
GlobalWebRateLimitNum = GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT", 60)
GlobalWebRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT_DURATION", 180))
}

View File

@@ -4,32 +4,39 @@ import (
"one-api/common"
)
var StreamingTimeout = common.GetEnvOrDefault("STREAMING_TIMEOUT", 60)
var DifyDebug = common.GetEnvOrDefaultBool("DIFY_DEBUG", true)
var MaxFileDownloadMB = common.GetEnvOrDefault("MAX_FILE_DOWNLOAD_MB", 20)
// ForceStreamOption 覆盖请求参数强制返回usage信息
var ForceStreamOption = common.GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true)
var GetMediaToken = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN", true)
var GetMediaTokenNotStream = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN_NOT_STREAM", true)
var UpdateTask = common.GetEnvOrDefaultBool("UPDATE_TASK", true)
var AzureDefaultAPIVersion = common.GetEnvOrDefaultString("AZURE_DEFAULT_API_VERSION", "2024-12-01-preview")
var StreamingTimeout int
var DifyDebug bool
var MaxFileDownloadMB int
var ForceStreamOption bool
var GetMediaToken bool
var GetMediaTokenNotStream bool
var UpdateTask bool
var AzureDefaultAPIVersion string
var GeminiVisionMaxImageNum int
var NotifyLimitCount int
var NotificationLimitDurationMinute int
var GenerateDefaultToken bool
//var GeminiModelMap = map[string]string{
// "gemini-1.0-pro": "v1",
//}
var GeminiVisionMaxImageNum = common.GetEnvOrDefault("GEMINI_VISION_MAX_IMAGE_NUM", 16)
var NotifyLimitCount = common.GetEnvOrDefault("NOTIFY_LIMIT_COUNT", 2)
var NotificationLimitDurationMinute = common.GetEnvOrDefault("NOTIFICATION_LIMIT_DURATION_MINUTE", 10)
func InitEnv() {
StreamingTimeout = common.GetEnvOrDefault("STREAMING_TIMEOUT", 60)
DifyDebug = common.GetEnvOrDefaultBool("DIFY_DEBUG", true)
MaxFileDownloadMB = common.GetEnvOrDefault("MAX_FILE_DOWNLOAD_MB", 20)
// ForceStreamOption 覆盖请求参数强制返回usage信息
ForceStreamOption = common.GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true)
GetMediaToken = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN", true)
GetMediaTokenNotStream = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN_NOT_STREAM", true)
UpdateTask = common.GetEnvOrDefaultBool("UPDATE_TASK", true)
AzureDefaultAPIVersion = common.GetEnvOrDefaultString("AZURE_DEFAULT_API_VERSION", "2024-12-01-preview")
GeminiVisionMaxImageNum = common.GetEnvOrDefault("GEMINI_VISION_MAX_IMAGE_NUM", 16)
NotifyLimitCount = common.GetEnvOrDefault("NOTIFY_LIMIT_COUNT", 2)
NotificationLimitDurationMinute = common.GetEnvOrDefault("NOTIFICATION_LIMIT_DURATION_MINUTE", 10)
// GenerateDefaultToken 是否生成初始令牌,默认关闭。
GenerateDefaultToken = common.GetEnvOrDefaultBool("GENERATE_DEFAULT_TOKEN", false)
//modelVersionMapStr := strings.TrimSpace(os.Getenv("GEMINI_MODEL_MAP"))
//if modelVersionMapStr == "" {
// return
@@ -43,6 +50,3 @@ func InitEnv() {
// }
//}
}
// GenerateDefaultToken 是否生成初始令牌,默认关闭。
var GenerateDefaultToken = common.GetEnvOrDefaultBool("GENERATE_DEFAULT_TOKEN", false)

View File

@@ -192,6 +192,8 @@ func buildTestRequest(model string) *dto.GeneralOpenAIRequest {
if !strings.Contains(model, "claude") {
testRequest.MaxTokens = 50
}
} else if strings.Contains(model, "gemini") {
testRequest.MaxTokens = 300
} else {
testRequest.MaxTokens = 10
}

View File

@@ -119,6 +119,9 @@ func FetchUpstreamModels(c *gin.Context) {
baseURL = channel.GetBaseURL()
}
url := fmt.Sprintf("%s/v1/models", baseURL)
if channel.Type == common.ChannelTypeGemini {
url = fmt.Sprintf("%s/v1beta/openai/models", baseURL)
}
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -139,7 +142,11 @@ func FetchUpstreamModels(c *gin.Context) {
var ids []string
for _, model := range result.Data {
ids = append(ids, model.ID)
id := model.ID
if channel.Type == common.ChannelTypeGemini {
id = strings.TrimPrefix(id, "models/")
}
ids = append(ids, id)
}
c.JSON(http.StatusOK, gin.H{

View File

@@ -12,6 +12,7 @@ import (
"one-api/model"
"one-api/router"
"one-api/service"
"one-api/setting/operation_setting"
"os"
"strconv"
@@ -51,6 +52,9 @@ func main() {
if err != nil {
common.FatalLog("failed to initialize database: " + err.Error())
}
model.CheckSetup()
// Initialize SQL Database
err = model.InitLogDB()
if err != nil {
@@ -69,10 +73,13 @@ func main() {
common.FatalLog("failed to initialize Redis: " + err.Error())
}
// Initialize model settings
operation_setting.InitModelSettings()
// Initialize constants
constant.InitEnv()
// Initialize options
model.InitOptionMap()
if common.RedisEnabled {
// for compatibility with old versions
common.MemoryCacheEnabled = true

View File

@@ -56,23 +56,30 @@ func createRootAccountIfNeed() error {
return nil
}
func checkSetup() {
if GetSetup() == nil {
func CheckSetup() {
setup := GetSetup()
if setup == nil {
// No setup record exists, check if we have a root user
if RootUserExists() {
common.SysLog("system is not initialized, but root user exists")
// Create setup record
setup := Setup{
newSetup := Setup{
Version: common.Version,
InitializedAt: time.Now().Unix(),
}
err := DB.Create(&setup).Error
err := DB.Create(&newSetup).Error
if err != nil {
common.SysLog("failed to create setup record: " + err.Error())
}
constant.Setup = true
} else {
common.SysLog("system is not initialized and no root user exists")
constant.Setup = false
}
} else {
// Setup record exists, system is initialized
common.SysLog("system is already initialized at: " + time.Unix(setup.InitializedAt, 0).String())
constant.Setup = true
}
}
@@ -237,7 +244,6 @@ func migrateDB() error {
}
err = DB.AutoMigrate(&Setup{})
common.SysLog("database migrated")
checkSetup()
//err = createRootAccountIfNeed()
return err
}

View File

@@ -16,6 +16,8 @@ var ModelList = []string{
"gemini-2.0-pro-exp",
// thinking exp
"gemini-2.0-flash-thinking-exp",
"gemini-2.5-pro-exp-03-25",
"gemini-2.5-pro-preview-03-25",
// imagen models
"imagen-3.0-generate-002",
// embedding models

View File

@@ -0,0 +1,106 @@
package xai
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"io"
"net/http"
"one-api/dto"
"one-api/relay/channel"
"one-api/relay/channel/openai"
relaycommon "one-api/relay/common"
"strings"
)
type Adaptor struct {
}
func (a *Adaptor) ConvertClaudeRequest(*gin.Context, *relaycommon.RelayInfo, *dto.ClaudeRequest) (any, error) {
//TODO implement me
//panic("implement me")
return nil, errors.New("not available")
}
func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
//not available
return nil, errors.New("not available")
}
func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
request.Size = ""
return request, nil
}
func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
}
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
return fmt.Sprintf("%s/v1/chat/completions", info.BaseUrl), nil
}
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
channel.SetupApiRequestHeader(info, c, req)
req.Set("Authorization", "Bearer "+info.ApiKey)
return nil
}
func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) {
if request == nil {
return nil, errors.New("request is nil")
}
request.StreamOptions = nil
if strings.HasPrefix(request.Model, "grok-3-mini") {
if request.MaxCompletionTokens == 0 && request.MaxTokens != 0 {
request.MaxCompletionTokens = request.MaxTokens
request.MaxTokens = 0
}
if strings.HasSuffix(request.Model, "-high") {
request.ReasoningEffort = "high"
request.Model = strings.TrimSuffix(request.Model, "-high")
} else if strings.HasSuffix(request.Model, "-low") {
request.ReasoningEffort = "low"
request.Model = strings.TrimSuffix(request.Model, "-low")
} else if strings.HasSuffix(request.Model, "-medium") {
request.ReasoningEffort = "medium"
request.Model = strings.TrimSuffix(request.Model, "-medium")
}
info.ReasoningEffort = request.ReasoningEffort
info.UpstreamModelName = request.Model
}
return request, nil
}
func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
return nil, nil
}
func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
//not available
return nil, errors.New("not available")
}
func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
return channel.DoApiRequest(a, c, info, requestBody)
}
func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *dto.OpenAIErrorWithStatusCode) {
if info.IsStream {
err, usage = openai.OaiStreamHandler(c, resp, info)
} else {
err, usage = openai.OpenaiHandler(c, resp, info)
}
if _, ok := usage.(*dto.Usage); ok && usage != nil {
usage.(*dto.Usage).CompletionTokens = usage.(*dto.Usage).TotalTokens - usage.(*dto.Usage).PromptTokens
}
return
}
func (a *Adaptor) GetModelList() []string {
return ModelList
}
func (a *Adaptor) GetChannelName() string {
return ChannelName
}

View File

@@ -0,0 +1,18 @@
package xai
var ModelList = []string{
// grok-3
"grok-3-beta", "grok-3-mini-beta",
// grok-3 mini
"grok-3-fast-beta", "grok-3-mini-fast-beta",
// extend grok-3-mini reasoning
"grok-3-mini-beta-high", "grok-3-mini-beta-low", "grok-3-mini-beta-medium",
"grok-3-mini-fast-beta-high", "grok-3-mini-fast-beta-low", "grok-3-mini-fast-beta-medium",
// image model
"grok-2-image",
// legacy models
"grok-2", "grok-2-vision",
"grok-beta", "grok-vision-beta",
}
var ChannelName = "xai"

View File

@@ -32,6 +32,7 @@ const (
APITypeBaiduV2
APITypeOpenRouter
APITypeXinference
APITypeXai
APITypeDummy // this one is only for count, do not add any channel after this
)
@@ -92,6 +93,8 @@ func ChannelType2APIType(channelType int) (int, bool) {
apiType = APITypeOpenRouter
case common.ChannelTypeXinference:
apiType = APITypeXinference
case common.ChannelTypeXai:
apiType = APITypeXai
}
if apiType == -1 {
return APITypeOpenAI, false

View File

@@ -25,6 +25,7 @@ import (
"one-api/relay/channel/tencent"
"one-api/relay/channel/vertex"
"one-api/relay/channel/volcengine"
"one-api/relay/channel/xai"
"one-api/relay/channel/xunfei"
"one-api/relay/channel/zhipu"
"one-api/relay/channel/zhipu_4v"
@@ -85,6 +86,8 @@ func GetAdaptor(apiType int) channel.Adaptor {
return &openai.Adaptor{}
case constant.APITypeXinference:
return &openai.Adaptor{}
case constant.APITypeXai:
return &xai.Adaptor{}
}
return nil
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"one-api/common"
"sync"
)
var groupRatio = map[string]float64{
@@ -11,8 +12,12 @@ var groupRatio = map[string]float64{
"vip": 1,
"svip": 1,
}
var groupRatioMutex sync.RWMutex
func GetGroupRatioCopy() map[string]float64 {
groupRatioMutex.RLock()
defer groupRatioMutex.RUnlock()
groupRatioCopy := make(map[string]float64)
for k, v := range groupRatio {
groupRatioCopy[k] = v
@@ -21,11 +26,17 @@ func GetGroupRatioCopy() map[string]float64 {
}
func ContainsGroupRatio(name string) bool {
groupRatioMutex.RLock()
defer groupRatioMutex.RUnlock()
_, ok := groupRatio[name]
return ok
}
func GroupRatio2JSONString() string {
groupRatioMutex.RLock()
defer groupRatioMutex.RUnlock()
jsonBytes, err := json.Marshal(groupRatio)
if err != nil {
common.SysError("error marshalling model ratio: " + err.Error())
@@ -34,11 +45,17 @@ func GroupRatio2JSONString() string {
}
func UpdateGroupRatioByJSONString(jsonStr string) error {
groupRatioMutex.Lock()
defer groupRatioMutex.Unlock()
groupRatio = make(map[string]float64)
return json.Unmarshal([]byte(jsonStr), &groupRatio)
}
func GetGroupRatio(name string) float64 {
groupRatioMutex.RLock()
defer groupRatioMutex.RUnlock()
ratio, ok := groupRatio[name]
if !ok {
common.SysError("group ratio not found: " + name)

View File

@@ -56,17 +56,15 @@ var cacheRatioMapMutex sync.RWMutex
// GetCacheRatioMap returns the cache ratio map
func GetCacheRatioMap() map[string]float64 {
cacheRatioMapMutex.Lock()
defer cacheRatioMapMutex.Unlock()
if cacheRatioMap == nil {
cacheRatioMap = defaultCacheRatio
}
cacheRatioMapMutex.RLock()
defer cacheRatioMapMutex.RUnlock()
return cacheRatioMap
}
// CacheRatio2JSONString converts the cache ratio map to a JSON string
func CacheRatio2JSONString() string {
GetCacheRatioMap()
cacheRatioMapMutex.RLock()
defer cacheRatioMapMutex.RUnlock()
jsonBytes, err := json.Marshal(cacheRatioMap)
if err != nil {
common.SysError("error marshalling cache ratio: " + err.Error())
@@ -84,10 +82,11 @@ func UpdateCacheRatioByJSONString(jsonStr string) error {
// GetCacheRatio returns the cache ratio for a model
func GetCacheRatio(name string) (float64, bool) {
GetCacheRatioMap()
cacheRatioMapMutex.RLock()
defer cacheRatioMapMutex.RUnlock()
ratio, ok := cacheRatioMap[name]
if !ok {
return 1, false // Default to 0.5 if not found
return 1, false // Default to 1 if not found
}
return ratio, true
}

View File

@@ -131,17 +131,12 @@ var defaultModelRatio = map[string]float64{
"bge-large-en": 0.002 * RMB,
"tao-8k": 0.002 * RMB,
"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
"gemini-1.0-pro-vision-001": 1,
"gemini-1.0-pro-001": 1,
"gemini-1.5-pro-latest": 1.75, // $3.5 / 1M tokens
"gemini-1.5-pro-exp-0827": 1.75, // $3.5 / 1M tokens
"gemini-1.5-flash-latest": 1,
"gemini-1.5-flash-exp-0827": 1,
"gemini-1.0-pro-latest": 1,
"gemini-1.0-pro-vision-latest": 1,
"gemini-ultra": 1,
"gemini-1.5-pro-latest": 1.25, // $3.5 / 1M tokens
"gemini-1.5-flash-latest": 0.075,
"gemini-2.0-flash": 0.05,
"gemini-2.5-pro-exp-03-25": 0.625,
"gemini-2.5-pro-preview-03-25": 0.625,
"text-embedding-004": 0.001,
"chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens
"chatglm_pro": 0.7143, // ¥0.01 / 1k tokens
"chatglm_std": 0.3572, // ¥0.005 / 1k tokens
@@ -204,29 +199,39 @@ var defaultModelRatio = map[string]float64{
"llama-3-sonar-small-32k-online": 0.2 / 1000 * USD,
"llama-3-sonar-large-32k-chat": 1 / 1000 * USD,
"llama-3-sonar-large-32k-online": 1 / 1000 * USD,
// grok
"grok-3-beta": 1.5,
"grok-3-mini-beta": 0.15,
"grok-2": 1,
"grok-2-vision": 1,
"grok-beta": 2.5,
"grok-vision-beta": 2.5,
"grok-3-fast-beta": 2.5,
"grok-3-mini-fast-beta": 0.3,
}
var defaultModelPrice = map[string]float64{
"suno_music": 0.1,
"suno_lyrics": 0.01,
"dall-e-3": 0.04,
"gpt-4-gizmo-*": 0.1,
"mj_imagine": 0.1,
"mj_variation": 0.1,
"mj_reroll": 0.1,
"mj_blend": 0.1,
"mj_modal": 0.1,
"mj_zoom": 0.1,
"mj_shorten": 0.1,
"mj_high_variation": 0.1,
"mj_low_variation": 0.1,
"mj_pan": 0.1,
"mj_inpaint": 0,
"mj_custom_zoom": 0,
"mj_describe": 0.05,
"mj_upscale": 0.05,
"swap_face": 0.05,
"mj_upload": 0.05,
"suno_music": 0.1,
"suno_lyrics": 0.01,
"dall-e-3": 0.04,
"imagen-3.0-generate-002": 0.03,
"gpt-4-gizmo-*": 0.1,
"mj_imagine": 0.1,
"mj_variation": 0.1,
"mj_reroll": 0.1,
"mj_blend": 0.1,
"mj_modal": 0.1,
"mj_zoom": 0.1,
"mj_shorten": 0.1,
"mj_high_variation": 0.1,
"mj_low_variation": 0.1,
"mj_pan": 0.1,
"mj_inpaint": 0,
"mj_custom_zoom": 0,
"mj_describe": 0.05,
"mj_upscale": 0.05,
"swap_face": 0.05,
"mj_upload": 0.05,
}
var (
@@ -249,17 +254,41 @@ var defaultCompletionRatio = map[string]float64{
"gpt-4-all": 2,
}
func GetModelPriceMap() map[string]float64 {
// InitModelSettings initializes all model related settings maps
func InitModelSettings() {
// Initialize modelPriceMap
modelPriceMapMutex.Lock()
defer modelPriceMapMutex.Unlock()
if modelPriceMap == nil {
modelPriceMap = defaultModelPrice
}
modelPriceMap = defaultModelPrice
modelPriceMapMutex.Unlock()
// Initialize modelRatioMap
modelRatioMapMutex.Lock()
modelRatioMap = defaultModelRatio
modelRatioMapMutex.Unlock()
// Initialize CompletionRatio
CompletionRatioMutex.Lock()
CompletionRatio = defaultCompletionRatio
CompletionRatioMutex.Unlock()
// Initialize cacheRatioMap
cacheRatioMapMutex.Lock()
cacheRatioMap = defaultCacheRatio
cacheRatioMapMutex.Unlock()
common.SysLog("model settings initialized")
}
func GetModelPriceMap() map[string]float64 {
modelPriceMapMutex.RLock()
defer modelPriceMapMutex.RUnlock()
return modelPriceMap
}
func ModelPrice2JSONString() string {
GetModelPriceMap()
modelPriceMapMutex.RLock()
defer modelPriceMapMutex.RUnlock()
jsonBytes, err := json.Marshal(modelPriceMap)
if err != nil {
common.SysError("error marshalling model price: " + err.Error())
@@ -276,7 +305,9 @@ func UpdateModelPriceByJSONString(jsonStr string) error {
// GetModelPrice 返回模型的价格,如果模型不存在则返回-1false
func GetModelPrice(name string, printErr bool) (float64, bool) {
GetModelPriceMap()
modelPriceMapMutex.RLock()
defer modelPriceMapMutex.RUnlock()
if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*"
}
@@ -293,24 +324,6 @@ func GetModelPrice(name string, printErr bool) (float64, bool) {
return price, true
}
func GetModelRatioMap() map[string]float64 {
modelRatioMapMutex.Lock()
defer modelRatioMapMutex.Unlock()
if modelRatioMap == nil {
modelRatioMap = defaultModelRatio
}
return modelRatioMap
}
func ModelRatio2JSONString() string {
GetModelRatioMap()
jsonBytes, err := json.Marshal(modelRatioMap)
if err != nil {
common.SysError("error marshalling model ratio: " + err.Error())
}
return string(jsonBytes)
}
func UpdateModelRatioByJSONString(jsonStr string) error {
modelRatioMapMutex.Lock()
defer modelRatioMapMutex.Unlock()
@@ -319,7 +332,9 @@ func UpdateModelRatioByJSONString(jsonStr string) error {
}
func GetModelRatio(name string) (float64, bool) {
GetModelRatioMap()
modelRatioMapMutex.RLock()
defer modelRatioMapMutex.RUnlock()
if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*"
}
@@ -343,16 +358,15 @@ func GetDefaultModelRatioMap() map[string]float64 {
}
func GetCompletionRatioMap() map[string]float64 {
CompletionRatioMutex.Lock()
defer CompletionRatioMutex.Unlock()
if CompletionRatio == nil {
CompletionRatio = defaultCompletionRatio
}
CompletionRatioMutex.RLock()
defer CompletionRatioMutex.RUnlock()
return CompletionRatio
}
func CompletionRatio2JSONString() string {
GetCompletionRatioMap()
CompletionRatioMutex.RLock()
defer CompletionRatioMutex.RUnlock()
jsonBytes, err := json.Marshal(CompletionRatio)
if err != nil {
common.SysError("error marshalling completion ratio: " + err.Error())
@@ -368,7 +382,8 @@ func UpdateCompletionRatioByJSONString(jsonStr string) error {
}
func GetCompletionRatio(name string) float64 {
GetCompletionRatioMap()
CompletionRatioMutex.RLock()
defer CompletionRatioMutex.RUnlock()
if strings.Contains(name, "/") {
if ratio, ok := CompletionRatio[name]; ok {
@@ -438,7 +453,14 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) {
return 3, true
}
if strings.HasPrefix(name, "gemini-") {
return 4, true
if strings.HasPrefix(name, "gemini-1.5") {
return 4, true
} else if strings.HasPrefix(name, "gemini-2.0") {
return 4, true
} else if strings.HasPrefix(name, "gemini-2.5-pro-preview") {
return 8, true
}
return 4, false
}
if strings.HasPrefix(name, "command") {
switch name {
@@ -451,7 +473,7 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) {
case "command-r-plus-08-2024":
return 4, true
default:
return 4, true
return 4, false
}
}
// hint 只给官方上4倍率由于开源模型供应商自行定价不对其进行补全倍率进行强制对齐
@@ -508,3 +530,14 @@ func GetAudioCompletionRatio(name string) float64 {
}
return 2
}
func ModelRatio2JSONString() string {
modelRatioMapMutex.RLock()
defer modelRatioMapMutex.RUnlock()
jsonBytes, err := json.Marshal(modelRatioMap)
if err != nil {
common.SysError("error marshalling model ratio: " + err.Error())
}
return string(jsonBytes)
}

View File

@@ -23,7 +23,7 @@
"react-turnstile": "^1.0.5",
"semantic-ui-offline": "^2.5.0",
"semantic-ui-react": "^2.1.3",
"sse": "github:mpetazzoni/sse.js",
"sse": "https://github.com/mpetazzoni/sse.js",
"i18next": "^23.16.8",
"react-i18next": "^13.0.0",
"i18next-browser-languagedetector": "^7.2.0"

View File

@@ -26,6 +26,7 @@ import Playground from './pages/Playground/Playground.js';
import OAuth2Callback from "./components/OAuth2Callback.js";
import PersonalSetting from './components/PersonalSetting.js';
import Setup from './pages/Setup/index.js';
import SetupCheck from './components/SetupCheck';
const Home = lazy(() => import('./pages/Home'));
const Detail = lazy(() => import('./pages/Detail'));
@@ -35,7 +36,7 @@ function App() {
const location = useLocation();
return (
<>
<SetupCheck>
<Routes>
<Route
path='/'
@@ -286,7 +287,7 @@ function App() {
/>
<Route path='*' element={<NotFound />} />
</Routes>
</>
</SetupCheck>
);
}

View File

@@ -0,0 +1,18 @@
import React, { useContext, useEffect } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { StatusContext } from '../context/Status';
const SetupCheck = ({ children }) => {
const [statusState] = useContext(StatusContext);
const location = useLocation();
useEffect(() => {
if (statusState?.status?.setup === false && location.pathname !== '/setup') {
window.location.href = '/setup';
}
}, [statusState?.status?.setup, location.pathname]);
return children;
};
export default SetupCheck;

View File

@@ -619,7 +619,7 @@ const SystemSetting = () => {
允许通过 Telegram 进行登录
</Form.Checkbox>
<Form.Checkbox
field='oidc.enabled'
field="['oidc.enabled']"
noLabel
onChange={(e) => handleCheckboxChange('oidc.enabled', e)}
>
@@ -721,14 +721,14 @@ const SystemSetting = () => {
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}>
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Input
field='oidc.well_known'
field="['oidc.well_known']"
label='Well-Known URL'
placeholder='请输入 OIDC 的 Well-Known URL'
/>
</Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Input
field='oidc.client_id'
field="['oidc.client_id']"
label='Client ID'
placeholder='输入 OIDC 的 Client ID'
/>
@@ -737,7 +737,7 @@ const SystemSetting = () => {
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}>
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Input
field='oidc.client_secret'
field="['oidc.client_secret']"
label='Client Secret'
type='password'
placeholder='敏感信息不会发送到前端显示'
@@ -745,7 +745,7 @@ const SystemSetting = () => {
</Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Input
field='oidc.authorization_endpoint'
field="['oidc.authorization_endpoint']"
label='Authorization Endpoint'
placeholder='输入 OIDC 的 Authorization Endpoint'
/>
@@ -754,14 +754,14 @@ const SystemSetting = () => {
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}>
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Input
field='oidc.token_endpoint'
field="['oidc.token_endpoint']"
label='Token Endpoint'
placeholder='输入 OIDC 的 Token Endpoint'
/>
</Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Input
field='oidc.user_info_endpoint'
field="['oidc.user_info_endpoint']"
label='User Info Endpoint'
placeholder='输入 OIDC 的 Userinfo Endpoint'
/>

View File

@@ -115,4 +115,9 @@ export const CHANNEL_OPTIONS = [
color: 'blue',
label: '字节火山方舟、豆包、DeepSeek通用'
},
{
value: 48,
color: 'blue',
label: 'xAI'
}
];

View File

@@ -514,7 +514,7 @@
",图片演示。": "related image demo.",
"令牌创建成功,请在列表页面点击复制获取令牌!": "Token created successfully, please click copy on the list page to get the token!",
"代理": "Proxy",
"此项可选,用于通过代理站来进行 API 调用,请输入代理站地址格式为https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com",
"此项可选,用于通过自定义API地址来进行 API 调用,请输入API地址格式为https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com",
"取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?": "Canceling password login will cause all users (including administrators) who have not bound other login methods to be unable to log in via password, confirm cancel?",
"按照如下格式输入:": "Enter in the following format:",
"模型版本": "Model version",
@@ -1111,7 +1111,7 @@
"如果你对接的是上游One API或者New API等转发项目请使用OpenAI类型不要使用此类型除非你知道你在做什么。": "If you are connecting to upstream One API or New API forwarding projects, please use OpenAI type. Do not use this type unless you know what you are doing.",
"完整的 Base URL支持变量{model}": "Complete Base URL, supports variable {model}",
"请输入完整的URL例如https://api.openai.com/v1/chat/completions": "Please enter complete URL, e.g.: https://api.openai.com/v1/chat/completions",
"此项可选,用于通过代理站来进行 API 调用,末尾不要带/v1和/": "Optional for API calls through proxy sites, do not end with /v1 and /",
"此项可选,用于通过自定义API地址来进行 API 调用,末尾不要带/v1和/": "Optional for API calls through custom API address, do not add /v1 and / at the end",
"私有部署地址": "Private Deployment Address",
"请输入私有部署地址格式为https://fastgpt.run/api/openapi": "Please enter private deployment address, format: https://fastgpt.run/api/openapi",
"注意非Chat API请务必填写正确的API地址否则可能导致无法使用": "Note: For non-Chat API, please make sure to enter the correct API address, otherwise it may not work",
@@ -1272,7 +1272,7 @@
"通知邮箱": "Notification email",
"设置用于接收额度预警的邮箱地址,不填则使用账号绑定的邮箱": "Set the email address for receiving quota warning notifications, if not set, the email address bound to the account will be used",
"留空则使用账号绑定的邮箱": "If left blank, the email address bound to the account will be used",
"代理站地址": "Base URL",
"API地址": "Base URL",
"对于官方渠道new-api已经内置地址除非是第三方代理站点或者Azure的特殊接入地址否则不需要填写": "For official channels, the new-api has a built-in address. Unless it is a third-party proxy site or a special Azure access address, there is no need to fill it in",
"渠道额外设置": "Channel extra settings",
"参数覆盖": "Parameters override",

View File

@@ -556,13 +556,13 @@ const EditChannel = (props) => {
{inputs.type !== 3 && inputs.type !== 8 && inputs.type !== 22 && inputs.type !== 36 && inputs.type !== 45 && (
<>
<div style={{ marginTop: 10 }}>
<Typography.Text strong>{t('代理站地址')}</Typography.Text>
<Typography.Text strong>{t('API地址')}</Typography.Text>
</div>
<Tooltip content={t('对于官方渠道new-api已经内置地址除非是第三方代理站点或者Azure的特殊接入地址否则不需要填写')}>
<Input
label={t('代理站地址')}
label={t('API地址')}
name="base_url"
placeholder={t('此项可选,用于通过代理站来进行 API 调用,末尾不要带/v1和/')}
placeholder={t('此项可选,用于通过自定义API地址来进行 API 调用,末尾不要带/v1和/')}
onChange={(value) => {
handleInputChange('base_url', value);
}}

View File

@@ -66,13 +66,9 @@ const Home = () => {
};
useEffect(() => {
if (statusState.status?.setup === false) {
window.location.href = '/setup';
return;
}
displayNotice().then();
displayHomePageContent().then();
});
}, []);
return (
<>
@@ -116,6 +112,7 @@ const Home = () => {
https://github.com/Calcium-Ion/new-api
</a>
</p>
<p>
{t('协议')}
<a