mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-19 08:17:27 +00:00
Merge branch 'alpha'
This commit is contained in:
@@ -501,9 +501,10 @@ func validateChannel(channel *model.Channel, isAdd bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AddChannelRequest struct {
|
type AddChannelRequest struct {
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
MultiKeyMode constant.MultiKeyMode `json:"multi_key_mode"`
|
MultiKeyMode constant.MultiKeyMode `json:"multi_key_mode"`
|
||||||
Channel *model.Channel `json:"channel"`
|
BatchAddSetKeyPrefix2Name bool `json:"batch_add_set_key_prefix_2_name"`
|
||||||
|
Channel *model.Channel `json:"channel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getVertexArrayKeys(keys string) ([]string, error) {
|
func getVertexArrayKeys(keys string) ([]string, error) {
|
||||||
@@ -616,6 +617,13 @@ func AddChannel(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
localChannel := addChannelRequest.Channel
|
localChannel := addChannelRequest.Channel
|
||||||
localChannel.Key = key
|
localChannel.Key = key
|
||||||
|
if addChannelRequest.BatchAddSetKeyPrefix2Name && len(keys) > 1 {
|
||||||
|
keyPrefix := localChannel.Key
|
||||||
|
if len(localChannel.Key) > 8 {
|
||||||
|
keyPrefix = localChannel.Key[:8]
|
||||||
|
}
|
||||||
|
localChannel.Name = fmt.Sprintf("%s %s", localChannel.Name, keyPrefix)
|
||||||
|
}
|
||||||
channels = append(channels, *localChannel)
|
channels = append(channels, *localChannel)
|
||||||
}
|
}
|
||||||
err = model.BatchInsertChannels(channels)
|
err = model.BatchInsertChannels(channels)
|
||||||
|
|||||||
@@ -128,6 +128,33 @@ func UpdateOption(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "ImageRatio":
|
||||||
|
err = ratio_setting.UpdateImageRatioByJSONString(option.Value)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "图片倍率设置失败: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "AudioRatio":
|
||||||
|
err = ratio_setting.UpdateAudioRatioByJSONString(option.Value)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "音频倍率设置失败: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "AudioCompletionRatio":
|
||||||
|
err = ratio_setting.UpdateAudioCompletionRatioByJSONString(option.Value)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "音频补全倍率设置失败: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
case "ModelRequestRateLimitGroup":
|
case "ModelRequestRateLimitGroup":
|
||||||
err = setting.CheckModelRequestRateLimitGroup(option.Value.(string))
|
err = setting.CheckModelRequestRateLimitGroup(option.Value.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -112,6 +112,9 @@ func InitOptionMap() {
|
|||||||
common.OptionMap["GroupGroupRatio"] = ratio_setting.GroupGroupRatio2JSONString()
|
common.OptionMap["GroupGroupRatio"] = ratio_setting.GroupGroupRatio2JSONString()
|
||||||
common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString()
|
common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString()
|
||||||
common.OptionMap["CompletionRatio"] = ratio_setting.CompletionRatio2JSONString()
|
common.OptionMap["CompletionRatio"] = ratio_setting.CompletionRatio2JSONString()
|
||||||
|
common.OptionMap["ImageRatio"] = ratio_setting.ImageRatio2JSONString()
|
||||||
|
common.OptionMap["AudioRatio"] = ratio_setting.AudioRatio2JSONString()
|
||||||
|
common.OptionMap["AudioCompletionRatio"] = ratio_setting.AudioCompletionRatio2JSONString()
|
||||||
common.OptionMap["TopUpLink"] = common.TopUpLink
|
common.OptionMap["TopUpLink"] = common.TopUpLink
|
||||||
//common.OptionMap["ChatLink"] = common.ChatLink
|
//common.OptionMap["ChatLink"] = common.ChatLink
|
||||||
//common.OptionMap["ChatLink2"] = common.ChatLink2
|
//common.OptionMap["ChatLink2"] = common.ChatLink2
|
||||||
@@ -397,6 +400,12 @@ func updateOptionMap(key string, value string) (err error) {
|
|||||||
err = ratio_setting.UpdateModelPriceByJSONString(value)
|
err = ratio_setting.UpdateModelPriceByJSONString(value)
|
||||||
case "CacheRatio":
|
case "CacheRatio":
|
||||||
err = ratio_setting.UpdateCacheRatioByJSONString(value)
|
err = ratio_setting.UpdateCacheRatioByJSONString(value)
|
||||||
|
case "ImageRatio":
|
||||||
|
err = ratio_setting.UpdateImageRatioByJSONString(value)
|
||||||
|
case "AudioRatio":
|
||||||
|
err = ratio_setting.UpdateAudioRatioByJSONString(value)
|
||||||
|
case "AudioCompletionRatio":
|
||||||
|
err = ratio_setting.UpdateAudioCompletionRatioByJSONString(value)
|
||||||
case "TopUpLink":
|
case "TopUpLink":
|
||||||
common.TopUpLink = value
|
common.TopUpLink = value
|
||||||
//case "ChatLink":
|
//case "ChatLink":
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference?hl=zh-cn#blob
|
||||||
var geminiSupportedMimeTypes = map[string]bool{
|
var geminiSupportedMimeTypes = map[string]bool{
|
||||||
"application/pdf": true,
|
"application/pdf": true,
|
||||||
"audio/mpeg": true,
|
"audio/mpeg": true,
|
||||||
@@ -30,6 +31,7 @@ var geminiSupportedMimeTypes = map[string]bool{
|
|||||||
"audio/wav": true,
|
"audio/wav": true,
|
||||||
"image/png": true,
|
"image/png": true,
|
||||||
"image/jpeg": true,
|
"image/jpeg": true,
|
||||||
|
"image/webp": true,
|
||||||
"text/plain": true,
|
"text/plain": true,
|
||||||
"video/mov": true,
|
"video/mov": true,
|
||||||
"video/mpeg": true,
|
"video/mpeg": true,
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycom
|
|||||||
|
|
||||||
// BuildRequestURL constructs the upstream URL.
|
// BuildRequestURL constructs the upstream URL.
|
||||||
func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
||||||
|
if isNewAPIRelay(info.ApiKey) {
|
||||||
|
return fmt.Sprintf("%s/jimeng/?Action=CVSync2AsyncSubmitTask&Version=2022-08-31", a.baseURL), nil
|
||||||
|
}
|
||||||
return fmt.Sprintf("%s/?Action=CVSync2AsyncSubmitTask&Version=2022-08-31", a.baseURL), nil
|
return fmt.Sprintf("%s/?Action=CVSync2AsyncSubmitTask&Version=2022-08-31", a.baseURL), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +104,12 @@ func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, erro
|
|||||||
func (a *TaskAdaptor) BuildRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error {
|
func (a *TaskAdaptor) BuildRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error {
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
return a.signRequest(req, a.accessKey, a.secretKey)
|
if isNewAPIRelay(info.ApiKey) {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+info.ApiKey)
|
||||||
|
} else {
|
||||||
|
return a.signRequest(req, a.accessKey, a.secretKey)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildRequestBody converts request into Jimeng specific format.
|
// BuildRequestBody converts request into Jimeng specific format.
|
||||||
@@ -161,6 +169,9 @@ func (a *TaskAdaptor) FetchTask(baseUrl, key string, body map[string]any) (*http
|
|||||||
}
|
}
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/?Action=CVSync2AsyncGetResult&Version=2022-08-31", baseUrl)
|
uri := fmt.Sprintf("%s/?Action=CVSync2AsyncGetResult&Version=2022-08-31", baseUrl)
|
||||||
|
if isNewAPIRelay(key) {
|
||||||
|
uri = fmt.Sprintf("%s/jimeng/?Action=CVSync2AsyncGetResult&Version=2022-08-31", a.baseURL)
|
||||||
|
}
|
||||||
payload := map[string]string{
|
payload := map[string]string{
|
||||||
"req_key": "jimeng_vgfm_t2v_l20", // This is fixed value from doc: https://www.volcengine.com/docs/85621/1544774
|
"req_key": "jimeng_vgfm_t2v_l20", // This is fixed value from doc: https://www.volcengine.com/docs/85621/1544774
|
||||||
"task_id": taskID,
|
"task_id": taskID,
|
||||||
@@ -178,17 +189,20 @@ func (a *TaskAdaptor) FetchTask(baseUrl, key string, body map[string]any) (*http
|
|||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
keyParts := strings.Split(key, "|")
|
if isNewAPIRelay(key) {
|
||||||
if len(keyParts) != 2 {
|
req.Header.Set("Authorization", "Bearer "+key)
|
||||||
return nil, fmt.Errorf("invalid api key format for jimeng: expected 'ak|sk'")
|
} else {
|
||||||
}
|
keyParts := strings.Split(key, "|")
|
||||||
accessKey := strings.TrimSpace(keyParts[0])
|
if len(keyParts) != 2 {
|
||||||
secretKey := strings.TrimSpace(keyParts[1])
|
return nil, fmt.Errorf("invalid api key format for jimeng: expected 'ak|sk'")
|
||||||
|
}
|
||||||
|
accessKey := strings.TrimSpace(keyParts[0])
|
||||||
|
secretKey := strings.TrimSpace(keyParts[1])
|
||||||
|
|
||||||
if err := a.signRequest(req, accessKey, secretKey); err != nil {
|
if err := a.signRequest(req, accessKey, secretKey); err != nil {
|
||||||
return nil, errors.Wrap(err, "sign request failed")
|
return nil, errors.Wrap(err, "sign request failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return service.GetHttpClient().Do(req)
|
return service.GetHttpClient().Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,3 +398,7 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e
|
|||||||
taskResult.Url = resTask.Data.VideoUrl
|
taskResult.Url = resTask.Data.VideoUrl
|
||||||
return &taskResult, nil
|
return &taskResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isNewAPIRelay(apiKey string) bool {
|
||||||
|
return strings.HasPrefix(apiKey, "sk-")
|
||||||
|
}
|
||||||
|
|||||||
@@ -117,6 +117,11 @@ func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycom
|
|||||||
// BuildRequestURL constructs the upstream URL.
|
// BuildRequestURL constructs the upstream URL.
|
||||||
func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
||||||
path := lo.Ternary(info.Action == constant.TaskActionGenerate, "/v1/videos/image2video", "/v1/videos/text2video")
|
path := lo.Ternary(info.Action == constant.TaskActionGenerate, "/v1/videos/image2video", "/v1/videos/text2video")
|
||||||
|
|
||||||
|
if isNewAPIRelay(info.ApiKey) {
|
||||||
|
return fmt.Sprintf("%s/kling%s", a.baseURL, path), nil
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s%s", a.baseURL, path), nil
|
return fmt.Sprintf("%s%s", a.baseURL, path), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +204,9 @@ func (a *TaskAdaptor) FetchTask(baseUrl, key string, body map[string]any) (*http
|
|||||||
}
|
}
|
||||||
path := lo.Ternary(action == constant.TaskActionGenerate, "/v1/videos/image2video", "/v1/videos/text2video")
|
path := lo.Ternary(action == constant.TaskActionGenerate, "/v1/videos/image2video", "/v1/videos/text2video")
|
||||||
url := fmt.Sprintf("%s%s/%s", baseUrl, path, taskID)
|
url := fmt.Sprintf("%s%s/%s", baseUrl, path, taskID)
|
||||||
|
if isNewAPIRelay(key) {
|
||||||
|
url = fmt.Sprintf("%s/kling%s/%s", baseUrl, path, taskID)
|
||||||
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -304,8 +312,13 @@ func (a *TaskAdaptor) createJWTToken() (string, error) {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) {
|
func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) {
|
||||||
|
if isNewAPIRelay(apiKey) {
|
||||||
|
return apiKey, nil // new api relay
|
||||||
|
}
|
||||||
keyParts := strings.Split(apiKey, "|")
|
keyParts := strings.Split(apiKey, "|")
|
||||||
|
if len(keyParts) != 2 {
|
||||||
|
return "", errors.New("invalid api_key, required format is accessKey|secretKey")
|
||||||
|
}
|
||||||
accessKey := strings.TrimSpace(keyParts[0])
|
accessKey := strings.TrimSpace(keyParts[0])
|
||||||
if len(keyParts) == 1 {
|
if len(keyParts) == 1 {
|
||||||
return accessKey, nil
|
return accessKey, nil
|
||||||
@@ -352,3 +365,7 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e
|
|||||||
}
|
}
|
||||||
return taskInfo, nil
|
return taskInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isNewAPIRelay(apiKey string) bool {
|
||||||
|
return strings.HasPrefix(apiKey, "sk-")
|
||||||
|
}
|
||||||
|
|||||||
@@ -90,41 +90,43 @@ func TextHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types
|
|||||||
|
|
||||||
if info.ChannelSetting.SystemPrompt != "" {
|
if info.ChannelSetting.SystemPrompt != "" {
|
||||||
// 如果有系统提示,则将其添加到请求中
|
// 如果有系统提示,则将其添加到请求中
|
||||||
request := convertedRequest.(*dto.GeneralOpenAIRequest)
|
request, ok := convertedRequest.(*dto.GeneralOpenAIRequest)
|
||||||
containSystemPrompt := false
|
if ok {
|
||||||
for _, message := range request.Messages {
|
containSystemPrompt := false
|
||||||
if message.Role == request.GetSystemRoleName() {
|
for _, message := range request.Messages {
|
||||||
containSystemPrompt = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !containSystemPrompt {
|
|
||||||
// 如果没有系统提示,则添加系统提示
|
|
||||||
systemMessage := dto.Message{
|
|
||||||
Role: request.GetSystemRoleName(),
|
|
||||||
Content: info.ChannelSetting.SystemPrompt,
|
|
||||||
}
|
|
||||||
request.Messages = append([]dto.Message{systemMessage}, request.Messages...)
|
|
||||||
} else if info.ChannelSetting.SystemPromptOverride {
|
|
||||||
common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true)
|
|
||||||
// 如果有系统提示,且允许覆盖,则拼接到前面
|
|
||||||
for i, message := range request.Messages {
|
|
||||||
if message.Role == request.GetSystemRoleName() {
|
if message.Role == request.GetSystemRoleName() {
|
||||||
if message.IsStringContent() {
|
containSystemPrompt = true
|
||||||
request.Messages[i].SetStringContent(info.ChannelSetting.SystemPrompt + "\n" + message.StringContent())
|
|
||||||
} else {
|
|
||||||
contents := message.ParseContent()
|
|
||||||
contents = append([]dto.MediaContent{
|
|
||||||
{
|
|
||||||
Type: dto.ContentTypeText,
|
|
||||||
Text: info.ChannelSetting.SystemPrompt,
|
|
||||||
},
|
|
||||||
}, contents...)
|
|
||||||
request.Messages[i].Content = contents
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !containSystemPrompt {
|
||||||
|
// 如果没有系统提示,则添加系统提示
|
||||||
|
systemMessage := dto.Message{
|
||||||
|
Role: request.GetSystemRoleName(),
|
||||||
|
Content: info.ChannelSetting.SystemPrompt,
|
||||||
|
}
|
||||||
|
request.Messages = append([]dto.Message{systemMessage}, request.Messages...)
|
||||||
|
} else if info.ChannelSetting.SystemPromptOverride {
|
||||||
|
common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true)
|
||||||
|
// 如果有系统提示,且允许覆盖,则拼接到前面
|
||||||
|
for i, message := range request.Messages {
|
||||||
|
if message.Role == request.GetSystemRoleName() {
|
||||||
|
if message.IsStringContent() {
|
||||||
|
request.Messages[i].SetStringContent(info.ChannelSetting.SystemPrompt + "\n" + message.StringContent())
|
||||||
|
} else {
|
||||||
|
contents := message.ParseContent()
|
||||||
|
contents = append([]dto.MediaContent{
|
||||||
|
{
|
||||||
|
Type: dto.ContentTypeText,
|
||||||
|
Text: info.ChannelSetting.SystemPrompt,
|
||||||
|
},
|
||||||
|
}, contents...)
|
||||||
|
request.Messages[i].Content = contents
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
|
|||||||
var cacheRatio float64
|
var cacheRatio float64
|
||||||
var imageRatio float64
|
var imageRatio float64
|
||||||
var cacheCreationRatio float64
|
var cacheCreationRatio float64
|
||||||
|
var audioRatio float64
|
||||||
|
var audioCompletionRatio float64
|
||||||
if !usePrice {
|
if !usePrice {
|
||||||
preConsumedTokens := common.Max(promptTokens, common.PreConsumedQuota)
|
preConsumedTokens := common.Max(promptTokens, common.PreConsumedQuota)
|
||||||
if meta.MaxTokens != 0 {
|
if meta.MaxTokens != 0 {
|
||||||
@@ -73,6 +75,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
|
|||||||
cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName)
|
cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName)
|
||||||
cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName)
|
cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName)
|
||||||
imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName)
|
imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName)
|
||||||
|
audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName)
|
||||||
|
audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName)
|
||||||
ratio := modelRatio * groupRatioInfo.GroupRatio
|
ratio := modelRatio * groupRatioInfo.GroupRatio
|
||||||
preConsumedQuota = int(float64(preConsumedTokens) * ratio)
|
preConsumedQuota = int(float64(preConsumedTokens) * ratio)
|
||||||
} else {
|
} else {
|
||||||
@@ -90,6 +94,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
|
|||||||
UsePrice: usePrice,
|
UsePrice: usePrice,
|
||||||
CacheRatio: cacheRatio,
|
CacheRatio: cacheRatio,
|
||||||
ImageRatio: imageRatio,
|
ImageRatio: imageRatio,
|
||||||
|
AudioRatio: audioRatio,
|
||||||
|
AudioCompletionRatio: audioCompletionRatio,
|
||||||
CacheCreationRatio: cacheCreationRatio,
|
CacheCreationRatio: cacheCreationRatio,
|
||||||
ShouldPreConsumedQuota: preConsumedQuota,
|
ShouldPreConsumedQuota: preConsumedQuota,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func ReturnPreConsumedQuota(c *gin.Context, relayInfo *relaycommon.RelayInfo) {
|
|||||||
gopool.Go(func() {
|
gopool.Go(func() {
|
||||||
relayInfoCopy := *relayInfo
|
relayInfoCopy := *relayInfo
|
||||||
|
|
||||||
err := PostConsumeQuota(&relayInfoCopy, -relayInfo.FinalPreConsumedQuota, 0, false)
|
err := PostConsumeQuota(&relayInfoCopy, -relayInfoCopy.FinalPreConsumedQuota, 0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.SysLog("error return pre-consumed quota: " + err.Error())
|
common.SysLog("error return pre-consumed quota: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,6 +279,18 @@ var defaultModelPrice = map[string]float64{
|
|||||||
"mj_upload": 0.05,
|
"mj_upload": 0.05,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultAudioRatio = map[string]float64{
|
||||||
|
"gpt-4o-audio-preview": 16,
|
||||||
|
"gpt-4o-mini-audio-preview": 66.67,
|
||||||
|
"gpt-4o-realtime-preview": 8,
|
||||||
|
"gpt-4o-mini-realtime-preview": 16.67,
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultAudioCompletionRatio = map[string]float64{
|
||||||
|
"gpt-4o-realtime": 2,
|
||||||
|
"gpt-4o-mini-realtime": 2,
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
modelPriceMap map[string]float64 = nil
|
modelPriceMap map[string]float64 = nil
|
||||||
modelPriceMapMutex = sync.RWMutex{}
|
modelPriceMapMutex = sync.RWMutex{}
|
||||||
@@ -327,6 +339,15 @@ func InitRatioSettings() {
|
|||||||
imageRatioMap = defaultImageRatio
|
imageRatioMap = defaultImageRatio
|
||||||
imageRatioMapMutex.Unlock()
|
imageRatioMapMutex.Unlock()
|
||||||
|
|
||||||
|
// initialize audioRatioMap
|
||||||
|
audioRatioMapMutex.Lock()
|
||||||
|
audioRatioMap = defaultAudioRatio
|
||||||
|
audioRatioMapMutex.Unlock()
|
||||||
|
|
||||||
|
// initialize audioCompletionRatioMap
|
||||||
|
audioCompletionRatioMapMutex.Lock()
|
||||||
|
audioCompletionRatioMap = defaultAudioCompletionRatio
|
||||||
|
audioCompletionRatioMapMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetModelPriceMap() map[string]float64 {
|
func GetModelPriceMap() map[string]float64 {
|
||||||
@@ -418,6 +439,18 @@ func GetDefaultModelRatioMap() map[string]float64 {
|
|||||||
return defaultModelRatio
|
return defaultModelRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDefaultImageRatioMap() map[string]float64 {
|
||||||
|
return defaultImageRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultAudioRatioMap() map[string]float64 {
|
||||||
|
return defaultAudioRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultAudioCompletionRatioMap() map[string]float64 {
|
||||||
|
return defaultAudioCompletionRatio
|
||||||
|
}
|
||||||
|
|
||||||
func GetCompletionRatioMap() map[string]float64 {
|
func GetCompletionRatioMap() map[string]float64 {
|
||||||
CompletionRatioMutex.RLock()
|
CompletionRatioMutex.RLock()
|
||||||
defer CompletionRatioMutex.RUnlock()
|
defer CompletionRatioMutex.RUnlock()
|
||||||
@@ -585,32 +618,22 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetAudioRatio(name string) float64 {
|
func GetAudioRatio(name string) float64 {
|
||||||
if strings.Contains(name, "-realtime") {
|
audioRatioMapMutex.RLock()
|
||||||
if strings.HasSuffix(name, "gpt-4o-realtime-preview") {
|
defer audioRatioMapMutex.RUnlock()
|
||||||
return 8
|
name = FormatMatchingModelName(name)
|
||||||
} else if strings.Contains(name, "gpt-4o-mini-realtime-preview") {
|
if ratio, ok := audioRatioMap[name]; ok {
|
||||||
return 10 / 0.6
|
return ratio
|
||||||
} else {
|
|
||||||
return 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.Contains(name, "-audio") {
|
|
||||||
if strings.HasPrefix(name, "gpt-4o-audio-preview") {
|
|
||||||
return 40 / 2.5
|
|
||||||
} else if strings.HasPrefix(name, "gpt-4o-mini-audio-preview") {
|
|
||||||
return 10 / 0.15
|
|
||||||
} else {
|
|
||||||
return 40
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 20
|
return 20
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAudioCompletionRatio(name string) float64 {
|
func GetAudioCompletionRatio(name string) float64 {
|
||||||
if strings.HasPrefix(name, "gpt-4o-realtime") {
|
audioCompletionRatioMapMutex.RLock()
|
||||||
return 2
|
defer audioCompletionRatioMapMutex.RUnlock()
|
||||||
} else if strings.HasPrefix(name, "gpt-4o-mini-realtime") {
|
name = FormatMatchingModelName(name)
|
||||||
return 2
|
if ratio, ok := audioCompletionRatioMap[name]; ok {
|
||||||
|
|
||||||
|
return ratio
|
||||||
}
|
}
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
@@ -631,6 +654,14 @@ var defaultImageRatio = map[string]float64{
|
|||||||
}
|
}
|
||||||
var imageRatioMap map[string]float64
|
var imageRatioMap map[string]float64
|
||||||
var imageRatioMapMutex sync.RWMutex
|
var imageRatioMapMutex sync.RWMutex
|
||||||
|
var (
|
||||||
|
audioRatioMap map[string]float64 = nil
|
||||||
|
audioRatioMapMutex = sync.RWMutex{}
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
audioCompletionRatioMap map[string]float64 = nil
|
||||||
|
audioCompletionRatioMapMutex = sync.RWMutex{}
|
||||||
|
)
|
||||||
|
|
||||||
func ImageRatio2JSONString() string {
|
func ImageRatio2JSONString() string {
|
||||||
imageRatioMapMutex.RLock()
|
imageRatioMapMutex.RLock()
|
||||||
@@ -659,6 +690,71 @@ func GetImageRatio(name string) (float64, bool) {
|
|||||||
return ratio, true
|
return ratio, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AudioRatio2JSONString() string {
|
||||||
|
audioRatioMapMutex.RLock()
|
||||||
|
defer audioRatioMapMutex.RUnlock()
|
||||||
|
jsonBytes, err := common.Marshal(audioRatioMap)
|
||||||
|
if err != nil {
|
||||||
|
common.SysError("error marshalling audio ratio: " + err.Error())
|
||||||
|
}
|
||||||
|
return string(jsonBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateAudioRatioByJSONString(jsonStr string) error {
|
||||||
|
|
||||||
|
tmp := make(map[string]float64)
|
||||||
|
if err := common.Unmarshal([]byte(jsonStr), &tmp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
audioRatioMapMutex.Lock()
|
||||||
|
audioRatioMap = tmp
|
||||||
|
audioRatioMapMutex.Unlock()
|
||||||
|
InvalidateExposedDataCache()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAudioRatioCopy() map[string]float64 {
|
||||||
|
audioRatioMapMutex.RLock()
|
||||||
|
defer audioRatioMapMutex.RUnlock()
|
||||||
|
copyMap := make(map[string]float64, len(audioRatioMap))
|
||||||
|
for k, v := range audioRatioMap {
|
||||||
|
copyMap[k] = v
|
||||||
|
}
|
||||||
|
return copyMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func AudioCompletionRatio2JSONString() string {
|
||||||
|
audioCompletionRatioMapMutex.RLock()
|
||||||
|
defer audioCompletionRatioMapMutex.RUnlock()
|
||||||
|
jsonBytes, err := common.Marshal(audioCompletionRatioMap)
|
||||||
|
if err != nil {
|
||||||
|
common.SysError("error marshalling audio completion ratio: " + err.Error())
|
||||||
|
}
|
||||||
|
return string(jsonBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateAudioCompletionRatioByJSONString(jsonStr string) error {
|
||||||
|
tmp := make(map[string]float64)
|
||||||
|
if err := common.Unmarshal([]byte(jsonStr), &tmp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
audioCompletionRatioMapMutex.Lock()
|
||||||
|
audioCompletionRatioMap = tmp
|
||||||
|
audioCompletionRatioMapMutex.Unlock()
|
||||||
|
InvalidateExposedDataCache()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAudioCompletionRatioCopy() map[string]float64 {
|
||||||
|
audioCompletionRatioMapMutex.RLock()
|
||||||
|
defer audioCompletionRatioMapMutex.RUnlock()
|
||||||
|
copyMap := make(map[string]float64, len(audioCompletionRatioMap))
|
||||||
|
for k, v := range audioCompletionRatioMap {
|
||||||
|
copyMap[k] = v
|
||||||
|
}
|
||||||
|
return copyMap
|
||||||
|
}
|
||||||
|
|
||||||
func GetModelRatioCopy() map[string]float64 {
|
func GetModelRatioCopy() map[string]float64 {
|
||||||
modelRatioMapMutex.RLock()
|
modelRatioMapMutex.RLock()
|
||||||
defer modelRatioMapMutex.RUnlock()
|
defer modelRatioMapMutex.RUnlock()
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ type PriceData struct {
|
|||||||
CacheRatio float64
|
CacheRatio float64
|
||||||
CacheCreationRatio float64
|
CacheCreationRatio float64
|
||||||
ImageRatio float64
|
ImageRatio float64
|
||||||
|
AudioRatio float64
|
||||||
|
AudioCompletionRatio float64
|
||||||
UsePrice bool
|
UsePrice bool
|
||||||
ShouldPreConsumedQuota int
|
ShouldPreConsumedQuota int
|
||||||
GroupRatioInfo GroupRatioInfo
|
GroupRatioInfo GroupRatioInfo
|
||||||
@@ -27,5 +29,5 @@ type PerCallPriceData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p PriceData) ToSetting() string {
|
func (p PriceData) ToSetting() string {
|
||||||
return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, ShouldPreConsumedQuota: %d, ImageRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.ShouldPreConsumedQuota, p.ImageRatio)
|
return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, ShouldPreConsumedQuota: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.ShouldPreConsumedQuota, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ const RatioSetting = () => {
|
|||||||
CompletionRatio: '',
|
CompletionRatio: '',
|
||||||
GroupRatio: '',
|
GroupRatio: '',
|
||||||
GroupGroupRatio: '',
|
GroupGroupRatio: '',
|
||||||
|
ImageRatio: '',
|
||||||
|
AudioRatio: '',
|
||||||
|
AudioCompletionRatio: '',
|
||||||
AutoGroups: '',
|
AutoGroups: '',
|
||||||
DefaultUseAutoGroup: false,
|
DefaultUseAutoGroup: false,
|
||||||
ExposeRatioEnabled: false,
|
ExposeRatioEnabled: false,
|
||||||
@@ -61,7 +64,10 @@ const RatioSetting = () => {
|
|||||||
item.key === 'UserUsableGroups' ||
|
item.key === 'UserUsableGroups' ||
|
||||||
item.key === 'CompletionRatio' ||
|
item.key === 'CompletionRatio' ||
|
||||||
item.key === 'ModelPrice' ||
|
item.key === 'ModelPrice' ||
|
||||||
item.key === 'CacheRatio'
|
item.key === 'CacheRatio' ||
|
||||||
|
item.key === 'ImageRatio' ||
|
||||||
|
item.key === 'AudioRatio' ||
|
||||||
|
item.key === 'AudioCompletionRatio'
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
item.value = JSON.stringify(JSON.parse(item.value), null, 2);
|
item.value = JSON.stringify(JSON.parse(item.value), null, 2);
|
||||||
|
|||||||
@@ -1999,6 +1999,16 @@
|
|||||||
"查看渠道密钥": "View channel key",
|
"查看渠道密钥": "View channel key",
|
||||||
"渠道密钥信息": "Channel key information",
|
"渠道密钥信息": "Channel key information",
|
||||||
"密钥获取成功": "Key acquisition successful",
|
"密钥获取成功": "Key acquisition successful",
|
||||||
|
"模型补全倍率(仅对自定义模型有效)": "Model completion ratio (only effective for custom models)",
|
||||||
|
"图片倍率": "Image ratio",
|
||||||
|
"音频倍率": "Audio ratio",
|
||||||
|
"音频补全倍率": "Audio completion ratio",
|
||||||
|
"图片输入相关的倍率设置,键为模型名称,值为倍率": "Image input related ratio settings, key is model name, value is ratio",
|
||||||
|
"音频输入相关的倍率设置,键为模型名称,值为倍率": "Audio input related ratio settings, key is model name, value is ratio",
|
||||||
|
"音频输出补全相关的倍率设置,键为模型名称,值为倍率": "Audio output completion related ratio settings, key is model name, value is ratio",
|
||||||
|
"为一个 JSON 文本,键为模型名称,值为倍率,例如:{\"gpt-image-1\": 2}": "A JSON text with model name as key and ratio as value, e.g.: {\"gpt-image-1\": 2}",
|
||||||
|
"为一个 JSON 文本,键为模型名称,值为倍率,例如:{\"gpt-4o-audio-preview\": 16}": "A JSON text with model name as key and ratio as value, e.g.: {\"gpt-4o-audio-preview\": 16}",
|
||||||
|
"为一个 JSON 文本,键为模型名称,值为倍率,例如:{\"gpt-4o-realtime\": 2}": "A JSON text with model name as key and ratio as value, e.g.: {\"gpt-4o-realtime\": 2}",
|
||||||
"顶栏管理": "Header Management",
|
"顶栏管理": "Header Management",
|
||||||
"控制顶栏模块显示状态,全局生效": "Control header module display status, global effect",
|
"控制顶栏模块显示状态,全局生效": "Control header module display status, global effect",
|
||||||
"用户主页,展示系统信息": "User homepage, displaying system information",
|
"用户主页,展示系统信息": "User homepage, displaying system information",
|
||||||
@@ -2058,7 +2068,7 @@
|
|||||||
"需要登录访问": "Require Login",
|
"需要登录访问": "Require Login",
|
||||||
"开启后未登录用户无法访问模型广场": "When enabled, unauthenticated users cannot access the model marketplace",
|
"开启后未登录用户无法访问模型广场": "When enabled, unauthenticated users cannot access the model marketplace",
|
||||||
"参与官方同步": "Participate in official sync",
|
"参与官方同步": "Participate in official sync",
|
||||||
"关闭后,此模型将不会被“同步官方”自动覆盖或创建": "When turned off, this model will be skipped by Sync official (no auto create/overwrite)",
|
"关闭后,此模型将不会被\"同步官方\"自动覆盖或创建": "When turned off, this model will be skipped by Sync official (no auto create/overwrite)",
|
||||||
"同步": "Sync",
|
"同步": "Sync",
|
||||||
"同步向导": "Sync Wizard",
|
"同步向导": "Sync Wizard",
|
||||||
"选择方式": "Select method",
|
"选择方式": "Select method",
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ export default function ModelRatioSettings(props) {
|
|||||||
ModelRatio: '',
|
ModelRatio: '',
|
||||||
CacheRatio: '',
|
CacheRatio: '',
|
||||||
CompletionRatio: '',
|
CompletionRatio: '',
|
||||||
|
ImageRatio: '',
|
||||||
|
AudioRatio: '',
|
||||||
|
AudioCompletionRatio: '',
|
||||||
ExposeRatioEnabled: false,
|
ExposeRatioEnabled: false,
|
||||||
});
|
});
|
||||||
const refForm = useRef();
|
const refForm = useRef();
|
||||||
@@ -219,6 +222,72 @@ export default function ModelRatioSettings(props) {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col xs={24} sm={16}>
|
||||||
|
<Form.TextArea
|
||||||
|
label={t('图片倍率')}
|
||||||
|
extraText={t('图片输入相关的倍率设置,键为模型名称,值为倍率')}
|
||||||
|
placeholder={t('为一个 JSON 文本,键为模型名称,值为倍率,例如:{"gpt-image-1": 2}')}
|
||||||
|
field={'ImageRatio'}
|
||||||
|
autosize={{ minRows: 6, maxRows: 12 }}
|
||||||
|
trigger='blur'
|
||||||
|
stopValidateWithError
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: (rule, value) => verifyJSON(value),
|
||||||
|
message: '不是合法的 JSON 字符串',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onChange={(value) =>
|
||||||
|
setInputs({ ...inputs, ImageRatio: value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col xs={24} sm={16}>
|
||||||
|
<Form.TextArea
|
||||||
|
label={t('音频倍率')}
|
||||||
|
extraText={t('音频输入相关的倍率设置,键为模型名称,值为倍率')}
|
||||||
|
placeholder={t('为一个 JSON 文本,键为模型名称,值为倍率,例如:{"gpt-4o-audio-preview": 16}')}
|
||||||
|
field={'AudioRatio'}
|
||||||
|
autosize={{ minRows: 6, maxRows: 12 }}
|
||||||
|
trigger='blur'
|
||||||
|
stopValidateWithError
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: (rule, value) => verifyJSON(value),
|
||||||
|
message: '不是合法的 JSON 字符串',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onChange={(value) =>
|
||||||
|
setInputs({ ...inputs, AudioRatio: value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col xs={24} sm={16}>
|
||||||
|
<Form.TextArea
|
||||||
|
label={t('音频补全倍率')}
|
||||||
|
extraText={t('音频输出补全相关的倍率设置,键为模型名称,值为倍率')}
|
||||||
|
placeholder={t('为一个 JSON 文本,键为模型名称,值为倍率,例如:{"gpt-4o-realtime": 2}')}
|
||||||
|
field={'AudioCompletionRatio'}
|
||||||
|
autosize={{ minRows: 6, maxRows: 12 }}
|
||||||
|
trigger='blur'
|
||||||
|
stopValidateWithError
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: (rule, value) => verifyJSON(value),
|
||||||
|
message: '不是合法的 JSON 字符串',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onChange={(value) =>
|
||||||
|
setInputs({ ...inputs, AudioCompletionRatio: value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={16}>
|
<Col span={16}>
|
||||||
<Form.Switch
|
<Form.Switch
|
||||||
|
|||||||
Reference in New Issue
Block a user