fix: translate error messages and comments to English for consistency

This commit is contained in:
Laisky.Cai 2025-01-27 03:34:27 +00:00
parent 59dba5bef3
commit d5fa98f2e0
25 changed files with 197 additions and 186 deletions

View File

@ -53,7 +53,7 @@ func getLarkUserInfoByCode(code string) (*LarkUser, error) {
res, err := client.Do(req)
if err != nil {
logger.SysLog(err.Error())
return nil, errors.New("None法连接至飞书服务器请稍后Retry")
return nil, errors.New("Unable to connect to Lark server, please try again later!")
}
defer res.Body.Close()
var oAuthResponse LarkOAuthResponse
@ -69,7 +69,7 @@ func getLarkUserInfoByCode(code string) (*LarkUser, error) {
res2, err := client.Do(req)
if err != nil {
logger.SysLog(err.Error())
return nil, errors.New("None法连接至飞书服务器请稍后Retry")
return nil, errors.New("Unable to connect to Lark server, please try again later!")
}
var larkUser LarkUser
err = json.NewDecoder(res2.Body).Decode(&larkUser)
@ -168,7 +168,7 @@ func LarkBind(c *gin.Context) {
if model.IsLarkIdAlreadyTaken(user.LarkId) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "该飞书账户已被Bind",
"message": "This Lark account has already been bound",
})
return
}

View File

@ -5,15 +5,16 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/logger"
"github.com/songquanpeng/one-api/controller"
"github.com/songquanpeng/one-api/model"
"net/http"
"strconv"
"time"
)
type OidcResponse struct {
@ -60,7 +61,7 @@ func getOidcUserInfoByCode(code string) (*OidcUser, error) {
res, err := client.Do(req)
if err != nil {
logger.SysLog(err.Error())
return nil, errors.New("None法连接至 OIDC 服务器请稍后Retry")
return nil, errors.New("Unable to connect to the OIDC server, please try again later!")
}
defer res.Body.Close()
var oidcResponse OidcResponse
@ -76,7 +77,7 @@ func getOidcUserInfoByCode(code string) (*OidcUser, error) {
res2, err := client.Do(req)
if err != nil {
logger.SysLog(err.Error())
return nil, errors.New("None法连接至 OIDC 服务器请稍后Retry")
return nil, errors.New("Unable to connect to the OIDC server, please try again later!")
}
var oidcUser OidcUser
err = json.NewDecoder(res2.Body).Decode(&oidcUser)
@ -104,7 +105,7 @@ func OidcAuth(c *gin.Context) {
if !config.OidcEnabled {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "Administrator未开启通过 OIDC Log in以及Sign up",
"message": "Administrator has not enabled OIDC Log in and Sign up",
})
return
}
@ -173,7 +174,7 @@ func OidcBind(c *gin.Context) {
if !config.OidcEnabled {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "Administrator未开启通过 OIDC Log in以及Sign up",
"message": "The administrator has turned off new user registration",
})
return
}
@ -192,7 +193,7 @@ func OidcBind(c *gin.Context) {
if model.IsOidcIdAlreadyTaken(user.OidcId) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "该 OIDC 账户已被Bind",
"message": "This OIDC account has already been bound",
})
return
}

View File

@ -3,14 +3,14 @@ package controller
import (
"encoding/json"
"fmt"
"github.com/songquanpeng/one-api/common"
"github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/message"
"github.com/songquanpeng/one-api/model"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common"
"github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/message"
"github.com/songquanpeng/one-api/model"
)
func GetStatus(c *gin.Context) {
@ -100,7 +100,7 @@ func SendEmailVerification(c *gin.Context) {
if !allowed {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "AdministratorEnable了邮箱域名白名单您的Email address的域名不在白名单中",
"message": "Administrator has enabled email domain whitelist, your email domain is not in the whitelist",
})
return
}
@ -154,8 +154,8 @@ func SendPasswordResetEmail(c *gin.Context) {
link := fmt.Sprintf("%s/user/reset?email=%s&token=%s", config.ServerAddress, email, code)
subject := fmt.Sprintf("%s Password reset", config.SystemName)
content := fmt.Sprintf("<p>Hello, you are resetting %s password.</p>"+
"<p>点击 <a href='%s'>此处</a> 进行Password reset。</p>"+
"<p>如果链接None法点击请尝试点击下面的链接或将其Copy到浏览器中打开<br> %s </p>"+
"<p>Click <a href='%s'>here</a> to reset your password.</p>"+
"<p>If the link cannot be clicked, please try clicking the link below or copy it to your browser to open:<br> %s </p>"+
"<p>The reset link is valid within %d minutes. If it is not your operation, please ignore it.</p>", config.SystemName, link, link, common.VerificationValidMinutes)
err := message.SendEmail(subject, email, content)
if err != nil {

View File

@ -2,13 +2,13 @@ package controller
import (
"encoding/json"
"github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/helper"
"github.com/songquanpeng/one-api/model"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/helper"
"github.com/songquanpeng/one-api/model"
)
func GetOptions(c *gin.Context) {
@ -47,7 +47,7 @@ func UpdateOption(c *gin.Context) {
if !config.ValidThemes[option.Value] {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "None效的主题",
"message": "invalid theme",
})
return
}
@ -55,7 +55,7 @@ func UpdateOption(c *gin.Context) {
if option.Value == "true" && config.GitHubClientId == "" {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "None法Enable GitHub OAuth请先填入 GitHub Client Id 以及 GitHub Client Secret",
"message": "Unable to enable GitHub OAuth, please fill in the GitHub Client Id and GitHub Client Secret first!",
})
return
}
@ -63,7 +63,7 @@ func UpdateOption(c *gin.Context) {
if option.Value == "true" && len(config.EmailDomainWhitelist) == 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "None法Enable邮箱域名限制请先填入限制的邮箱域名",
"message": "Unable to enable email domain restriction, please fill in the restricted email domains first!",
})
return
}

View File

@ -162,7 +162,7 @@ func AddToken(c *gin.Context) {
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": fmt.Sprintf("参数Error:%s", err.Error()),
"message": fmt.Sprintf("invalid token: %s", err.Error()),
})
return
}
@ -336,7 +336,7 @@ func UpdateToken(c *gin.Context) {
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": fmt.Sprintf("参数Error:%s", err.Error()),
"message": fmt.Sprintf("invalid token: %s", err.Error()),
})
return
}

View File

@ -271,7 +271,7 @@ func GetUserDashboard(c *gin.Context) {
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "None法获取Statistics",
"message": "Failed to get user dashboard data: " + err.Error(),
"data": nil,
})
return
@ -415,7 +415,7 @@ func UpdateUser(c *gin.Context) {
if myRole <= updatedUser.Role && myRole != model.RoleRootUser {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "None权将其他Users权限等级Promote到大于Equals自己的权限等级",
"message": "No permission to promote other users to a permission level greater than or equal to your own",
})
return
}
@ -529,7 +529,7 @@ func DeleteSelf(c *gin.Context) {
if user.Role == model.RoleRootUser {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "不能DeleteSuper administrator账户",
"message": "Cannot delete super administrator account",
})
return
}
@ -813,7 +813,7 @@ func AdminTopUp(c *gin.Context) {
return
}
if req.Remark == "" {
req.Remark = fmt.Sprintf("通过 API Recharge %s", common.LogQuota(int64(req.Quota)))
req.Remark = fmt.Sprintf("Recharged via API %s", common.LogQuota(int64(req.Quota)))
}
model.RecordTopupLog(req.UserId, req.Remark, req.Quota)
c.JSON(http.StatusOK, gin.H{

View File

@ -107,7 +107,7 @@ func TokenAuth() func(c *gin.Context) {
}
if token.Subnet != nil && *token.Subnet != "" {
if !network.IsIpInSubnets(ctx, c.ClientIP(), *token.Subnet) {
abortWithMessage(c, http.StatusForbidden, fmt.Sprintf("该API Keys只能在指定网段使用%s当前 ip%s", *token.Subnet, c.ClientIP()))
abortWithMessage(c, http.StatusForbidden, fmt.Sprintf("This API key can only be used in the specified subnet: %s, current IP: %s", *token.Subnet, c.ClientIP()))
return
}
}
@ -129,7 +129,7 @@ func TokenAuth() func(c *gin.Context) {
if token.Models != nil && *token.Models != "" {
c.Set(ctxkey.AvailableModels, *token.Models)
if requestModel != "" && !isModelInList(requestModel, *token.Models) {
abortWithMessage(c, http.StatusForbidden, fmt.Sprintf("该API KeysNone权使用Model%s", requestModel))
abortWithMessage(c, http.StatusForbidden, fmt.Sprintf("This API key does not have permission to use the model: %s", requestModel))
return
}
}

View File

@ -30,12 +30,12 @@ func Distribute() func(c *gin.Context) {
if ok {
id, err := strconv.Atoi(channelId.(string))
if err != nil {
abortWithMessage(c, http.StatusBadRequest, "None效的Channel Id")
abortWithMessage(c, http.StatusBadRequest, "Invalid Channel Id")
return
}
channel, err = model.GetChannelById(id, true)
if err != nil {
abortWithMessage(c, http.StatusBadRequest, "None效的Channel Id")
abortWithMessage(c, http.StatusBadRequest, "Invalid Channel Id")
return
}
if channel.Status != model.ChannelStatusEnabled {
@ -47,7 +47,7 @@ func Distribute() func(c *gin.Context) {
var err error
channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, requestModel, false)
if err != nil {
message := fmt.Sprintf("当前Group %s 下对于Model %s No available channels", userGroup, requestModel)
message := fmt.Sprintf("No available channels for Model %s under Group %s", requestModel, userGroup)
if channel != nil {
logger.SysError(fmt.Sprintf("Channel does not exist: %d", channel.Id))
message = "Database consistency has been broken, please contact the administrator"

View File

@ -2,6 +2,7 @@ package model
import (
"fmt"
"github.com/pkg/errors"
"github.com/songquanpeng/one-api/common"
"github.com/songquanpeng/one-api/common/helper"
@ -80,7 +81,7 @@ func Redeem(key string, userId int) (quota int64, err error) {
return err
})
if err != nil {
return 0, errors.New("Redeem失败," + err.Error())
return 0, errors.New("Redeem failed, " + err.Error())
}
RecordLog(userId, LogTypeTopup, fmt.Sprintf("Recharge %s through redemption code", common.LogQuota(redemption.Quota)))
return redemption.Quota, nil

View File

@ -72,7 +72,7 @@ func ValidateUserToken(key string) (token *Token, err error) {
return nil, errors.Wrap(err, "failed to get token by key")
}
if token.Status == TokenStatusExhausted {
return nil, fmt.Errorf("API Keys %s#%dQuota已用尽", token.Name, token.Id)
return nil, fmt.Errorf("API Key %s (#%d) quota has been exhausted", token.Name, token.Id)
} else if token.Status == TokenStatusExpired {
return nil, errors.New("The token has expired")
}

View File

@ -2,6 +2,7 @@ package monitor
import (
"fmt"
"github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/logger"
"github.com/songquanpeng/one-api/common/message"
@ -38,9 +39,10 @@ func DisableChannel(channelId int, channelName string, reason string) {
func MetricDisableChannel(channelId int, successRate float64) {
model.UpdateChannelStatusById(channelId, model.ChannelStatusAutoDisabled)
logger.SysLog(fmt.Sprintf("channel #%d has been disabled due to low success rate: %.2f", channelId, successRate*100))
subject := fmt.Sprintf("Channel #%d 已被Disable", channelId)
content := fmt.Sprintf("该Channel#%d在最近 %d 次调用中成功率为 %.2f%%,低于阈值 %.2f%%因此被System自动Disable。",
channelId, config.MetricQueueSize, successRate*100, config.MetricSuccessRateThreshold*100)
subject := fmt.Sprintf("Channel #%d has been disabled", channelId)
content := fmt.Sprintf("The channel (#%d) has been automatically disabled by the system due to "+
"a success rate of %.2f%% over the last %d calls, which is below the threshold of %.2f%%.",
channelId, successRate*100, config.MetricQueueSize, config.MetricSuccessRateThreshold*100)
notifyRootUser(subject, content)
}
@ -48,7 +50,7 @@ func MetricDisableChannel(channelId int, successRate float64) {
func EnableChannel(channelId int, channelName string) {
model.UpdateChannelStatusById(channelId, model.ChannelStatusEnabled)
logger.SysLog(fmt.Sprintf("channel #%d has been enabled", channelId))
subject := fmt.Sprintf("Channel「%s」#%d已被Enable", channelName, channelId)
content := fmt.Sprintf("Channel「%s」#%d已被Enable", channelName, channelId)
subject := fmt.Sprintf("Channel %s (#%d) has been enabled", channelName, channelId)
content := fmt.Sprintf("Channel %s (#%d) has been enabled", channelName, channelId)
notifyRootUser(subject, content)
}

View File

@ -35,7 +35,7 @@ func ShouldDisableChannel(err *model.Error, statusCode int) bool {
strings.Contains(lowerMessage, "balance") ||
strings.Contains(lowerMessage, "permission denied") ||
strings.Contains(lowerMessage, "organization has been restricted") || // groq
strings.Contains(lowerMessage, "已欠费") {
strings.Contains(lowerMessage, "overdue payment") {
return true
}
return false

View File

@ -38,7 +38,7 @@ func aiProxyDocuments2Markdown(documents []LibraryDocument) string {
if len(documents) == 0 {
return ""
}
content := "\n\n参考文档:\n"
content := "\n\nReference Documents:\n"
for i, document := range documents {
content += fmt.Sprintf("%d. [%s](%s)\n", i+1, document.Title, document.URL)
}

View File

@ -148,18 +148,18 @@ func responseAli2OpenAIImage(response *TaskResponse, responseFormat string) *ope
for _, data := range response.Output.Results {
var b64Json string
if responseFormat == "b64_json" {
// 读取 data.Url 的图片数据并转存到 b64Json
// Read the image data from data.Url and store it in b64Json
imageData, err := getImageData(data.Url)
if err != nil {
// 处理获取图片数据失败的情况
// Handle the case where getting image data fails
logger.SysError("getImageData Error getting image data: " + err.Error())
continue
}
// 将图片数据转为 Base64 编码的字符串
// Convert the image data to a Base64 encoded string
b64Json = Base64Encode(imageData)
} else {
// 如果 responseFormat 不是 "b64_json",则直接使用 data.B64Image
// If responseFormat is not "b64_json", use data.B64Image directly
b64Json = data.B64Image
}

View File

@ -2,23 +2,23 @@ package cohere
type Request struct {
Message string `json:"message" required:"true"`
Model string `json:"model,omitempty"` // Default值为"command-r"
Stream bool `json:"stream,omitempty"` // Default值为false
Model string `json:"model,omitempty"` // default to "command-r"
Stream bool `json:"stream,omitempty"` // default to false
Preamble string `json:"preamble,omitempty"`
ChatHistory []ChatMessage `json:"chat_history,omitempty"`
ConversationID string `json:"conversation_id,omitempty"`
PromptTruncation string `json:"prompt_truncation,omitempty"` // Default值为"AUTO"
PromptTruncation string `json:"prompt_truncation,omitempty"` // default to "AUTO"
Connectors []Connector `json:"connectors,omitempty"`
Documents []Document `json:"documents,omitempty"`
Temperature *float64 `json:"temperature,omitempty"` // Default值为0.3
Temperature *float64 `json:"temperature,omitempty"` // default to 0.3
MaxTokens int `json:"max_tokens,omitempty"`
MaxInputTokens int `json:"max_input_tokens,omitempty"`
K int `json:"k,omitempty"` // Default值为0
P *float64 `json:"p,omitempty"` // Default值为0.75
K int `json:"k,omitempty"` // default to 0
P *float64 `json:"p,omitempty"` // default to 0.75
Seed int `json:"seed,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"`
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // Default值为0.0
PresencePenalty *float64 `json:"presence_penalty,omitempty"` // Default值为0.0
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // default to 0.0
PresencePenalty *float64 `json:"presence_penalty,omitempty"` // default to 0.0
Tools []Tool `json:"tools,omitempty"`
ToolResults []ToolResult `json:"tool_results,omitempty"`
}

View File

@ -6,40 +6,46 @@ type Message struct {
}
type ChatRequest struct {
// Model nameOptional values包括 hunyuan-lite、hunyuan-standard、hunyuan-standard-256K、hunyuan-pro。
// 各Model介绍请阅读 [产品概述](https://cloud.tencent.com/document/product/1729/104753) 中的说明。
// Model name, optional values include hunyuan-lite, hunyuan-standard, hunyuan-standard-256K, hunyuan-pro.
// For descriptions of each model, please read the [Product Overview](https://cloud.tencent.com/document/product/1729/104753).
//
// Note
// 不同的Model计费不同请根据 [购买指南](https://cloud.tencent.com/document/product/1729/97731) 按需调用。
// Note:
// Different models have different pricing. Please refer to the [Purchase Guide](https://cloud.tencent.com/document/product/1729/97731) for details.
Model *string `json:"Model"`
// Chat上下文信息。
// 说明:
// 1. 长度最多为 40按对话Time从旧到新在数Group中排列。
// 2. Message.Role Optional valuessystem、user、assistant。
// 其中system 角色可选如存在则必须位于列表的最开始。user 和 assistant 需交替出现(一问一答),以 user 提问开始和结束,且 Content 不能为空。Role 的顺序示例:[system可选 user assistant user assistant user ...]。
// 3. Messages 中 Content 总长度不能超过ModelEnter长度上限可参考 [产品概述](https://cloud.tencent.com/document/product/1729/104753) 文档),超过则会截断最前面的内容,只保留尾部内容。
// Chat context information.
// Description:
// 1. The maximum length is 40, arranged in the array in chronological order from oldest to newest.
// 2. Message.Role optional values: system, user, assistant.
// Among them, the system role is optional. If it exists, it must be at the beginning of the list.
// User and assistant must alternate (one question and one answer), starting and ending with user,
// and Content cannot be empty. The order of roles is as follows: [system (optional) user assistant user assistant user ...].
// 3. The total length of Content in Messages cannot exceed the model's length limit
// (refer to the [Product Overview](https://cloud.tencent.com/document/product/1729/104753) document).
// If it exceeds, the earliest content will be truncated, leaving only the latest content.
Messages []*Message `json:"Messages"`
// 流式调用开关。
// 说明:
// 1. 未传值时Default为非流式调用false
// 2. 流式调用时以 SSE 协议增量返回结果(返回值取 Choices[n].Delta 中的值,需要拼接增量数据才能获得完整结果)。
// 3. 非流式调用时:
// 调用方式与普通 HTTP 请求None异。
// 接口响应耗时较长,**如需更低时延建议Settings为 true**。
// 只返回一次最终结果(返回值取 Choices[n].Message 中的值)。
// Stream call switch.
// Description:
// 1. If not provided, the default is non-streaming call (false).
// 2. In streaming calls, results are returned incrementally using the SSE protocol
// (the return value is taken from Choices[n].Delta, and incremental data needs to be concatenated to obtain the complete result).
// 3. In non-streaming calls:
// The call method is the same as a regular HTTP request.
// The interface response time is relatively long. **If lower latency is required, it is recommended to set this to true**.
// Only the final result is returned once (the return value is taken from Choices[n].Message).
//
// Note
// 通过 SDK 调用时,流式和非流式调用需用**不同的方式**获取返回值,具体参考 SDK 中的注释或示例(在各语言 SDK 代码仓库的 examples/hunyuan/v20230901/ 目录中)。
// Note:
// When calling through the SDK, different methods are required to obtain return values for streaming and non-streaming calls.
// Refer to the comments or examples in the SDK (in the examples/hunyuan/v20230901/ directory of each language SDK code repository).
Stream *bool `json:"Stream"`
// 说明:
// 1. 影响输出文本的多样性,取值越大,生成文本的多样性越强。
// 2. 取值区间为 [0.0, 1.0]未传值时使用各Model推荐值。
// 3. 非必要不建议使用,不合理的取值会影响效果。
// Description:
// 1. Affects the diversity of the output text. The larger the value, the more diverse the generated text.
// 2. The value range is [0.0, 1.0]. If not provided, the recommended value for each model is used.
// 3. It is not recommended to use this unless necessary, as unreasonable values can affect the results.
TopP *float64 `json:"TopP"`
// 说明:
// 1. 较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定。
// 2. 取值区间为 [0.0, 2.0]未传值时使用各Model推荐值。
// 3. 非必要不建议使用,不合理的取值会影响效果。
// Description:
// 1. Higher values make the output more random, while lower values make it more focused and deterministic.
// 2. The value range is [0.0, 2.0]. If not provided, the recommended value for each model is used.
// 3. It is not recommended to use this unless necessary, as unreasonable values can affect the results.
Temperature *float64 `json:"Temperature"`
}
@ -55,19 +61,19 @@ type Usage struct {
}
type ResponseChoices struct {
FinishReason string `json:"FinishReason,omitempty"` // 流式结束标志位,为 stop 则表示尾包
Messages Message `json:"Message,omitempty"` // 内容,同步模式返回内容,流模式为 null 输出 content 内容总数最多支持 1024token。
Delta Message `json:"Delta,omitempty"` // 内容,流模式返回内容,同步模式为 null 输出 content 内容总数最多支持 1024token。
FinishReason string `json:"FinishReason,omitempty"` // Stream end flag, "stop" indicates the end packet
Messages Message `json:"Message,omitempty"` // Content, returned in synchronous mode, null in stream mode. The total content supports up to 1024 tokens.
Delta Message `json:"Delta,omitempty"` // Content, returned in stream mode, null in synchronous mode. The total content supports up to 1024 tokens.
}
type ChatResponse struct {
Choices []ResponseChoices `json:"Choices,omitempty"` // 结果
Created int64 `json:"Created,omitempty"` // unix Time戳的字符串
Id string `json:"Id,omitempty"` // 会话 id
Usage Usage `json:"Usage,omitempty"` // token 数量
Error Error `json:"Error,omitempty"` // 错误信息 Note此字段可能返回 null表示取不到有效值
Note string `json:"Note,omitempty"` // 注释
ReqID string `json:"Req_id,omitempty"` // 唯一请求 Id每次请求都会返回。用于反馈接口入参
Choices []ResponseChoices `json:"Choices,omitempty"` // Results
Created int64 `json:"Created,omitempty"` // Unix timestamp string
Id string `json:"Id,omitempty"` // Session ID
Usage Usage `json:"Usage,omitempty"` // Token count
Error Error `json:"Error,omitempty"` // Error information. Note: This field may return null, indicating that no valid value was found.
Note string `json:"Note,omitempty"` // Note
ReqID string `json:"Req_id,omitempty"` // Unique request ID, returned with each request. Used for feedback on interface input parameters.
}
type ChatResponseP struct {

View File

@ -385,8 +385,9 @@ func GetAudioCompletionRatio(actualModelName string) float64 {
// AudioTokensPerSecond is the number of audio tokens per second for each model.
var AudioPromptTokensPerSecond = map[string]float64{
// whisper 的 API 价格是 $0.0001/sec。one-api 的历史倍率为 15对应 $0.03/kilo_tokens。
// 那么换算后可得,每秒的 tokens 应该为 0.0001/0.03*1000 = 3.3333
// Whisper API price is $0.0001/sec. One-api's historical ratio is 15,
// corresponding to $0.03/kilo_tokens.
// After conversion, tokens per second should be 0.0001/0.03*1000 = 3.3333.
"whisper-1": 0.0001 / 0.03 * 1000,
// gpt-4o-audio series processes 10 tokens per second
"gpt-4o-audio-preview": 10,

View File

@ -125,7 +125,7 @@ func postConsumeQuota(ctx context.Context, usage *relaymodel.Usage, meta *meta.M
}
var extraLog string
if systemPromptReset {
extraLog = " NoteSystemPrompt词已被重置"
extraLog = " (Note: System prompt has been reset)"
}
logContent := fmt.Sprintf("model rate %.2f, group rate %.2f, completion rate %.2f%s", modelRatio, groupRatio, completionRatio, extraLog)
model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, promptTokens, completionTokens, textRequest.Model, meta.TokenName, quota, logContent)

View File

@ -203,7 +203,7 @@ const ChannelsTable = () => {
trigger={<Label basic color='red'>
Disabled
</Label>}
content='本Channel被手动Disable'
content='This channel has been manually disabled'
basic
/>
);
@ -213,7 +213,7 @@ const ChannelsTable = () => {
trigger={<Label basic color='yellow'>
Disabled
</Label>}
content='本Channel被程序自动Disable'
content='This channel has been automatically disabled by the program'
basic
/>
);
@ -277,7 +277,7 @@ const ChannelsTable = () => {
newChannels[realIdx].response_time = time * 1000;
newChannels[realIdx].test_time = Date.now() / 1000;
setChannels(newChannels);
showInfo(`Channel ${name} Test成功Model ${model},耗时 ${time.toFixed(2)}s。`);
showInfo(`Channel ${name} tested successfully with model ${model}, taking ${time.toFixed(2)} seconds.`);
} else {
showError(message);
}
@ -292,7 +292,7 @@ const ChannelsTable = () => {
const res = await API.get(`/api/channel/test?scope=${scope}`);
const { success, message } = res.data;
if (success) {
showInfo('已成功开始TestChannel请Refresh页面查看结果。');
showInfo('Successfully started testing channels, please refresh the page to see the results.');
} else {
showError(message);
}
@ -302,7 +302,7 @@ const ChannelsTable = () => {
const res = await API.delete(`/api/channel/disabled`);
const { success, message, data } = res.data;
if (success) {
showSuccess(`已Delete所有DisableChannel共计 ${data}`);
showSuccess(`Successfully deleted all disabled channels, total ${data} channels`);
await refresh();
} else {
showError(message);
@ -504,26 +504,26 @@ const ChannelsTable = () => {
idx,
event.target.value
);
}}>
}}>
<input style={{ maxWidth: '60px' }} />
</Input>}
content='Channel priority - higher value means higher priority'
/>
</Table.Cell>
<Table.Cell hidden={!showDetail}>
<Dropdown
placeholder='请选择TestModel'
selection
options={channel.model_options}
defaultValue={channel.test_model}
onChange={(event, data) => {
</Input>}
content='Channel priority - higher value means higher priority'
/>
</Table.Cell>
<Table.Cell hidden={!showDetail}>
<Dropdown
placeholder='Please select TestModel'
selection
options={channel.model_options}
defaultValue={channel.test_model}
onChange={(event, data) => {
switchTestModel(idx, data.value);
}}
/>
</Table.Cell>
<Table.Cell>
<div>
<Button
}}
/>
</Table.Cell>
<Table.Cell>
<div>
<Button
size={'small'}
positive
onClick={() => {
@ -597,14 +597,14 @@ const ChannelsTable = () => {
Test all channels
</Button>
<Button size='small' loading={loading} onClick={()=>{testChannels("disabled")}}>
TestDisableChannel
Test disabled channels
</Button>
{/*<Button size='small' onClick={updateAllChannelsBalance}*/}
{/* loading={loading || updatingBalance}>Update the balance of enabled channels</Button>*/}
<Popup
trigger={
<Button size='small' loading={loading}>
DeleteDisableChannel
Delete disabled channels
</Button>
}
on='click'
@ -627,7 +627,7 @@ const ChannelsTable = () => {
}
/>
<Button size='small' onClick={refresh} loading={loading}>Refresh</Button>
<Button size='small' onClick={toggleShowDetail}>{showDetail ? "隐藏Details" : "Details"}</Button>
<Button size='small' onClick={toggleShowDetail}>{showDetail ? "Hide Details" : "Details"}</Button>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>

View File

@ -247,24 +247,24 @@ const OperationSetting = () => {
</Form.Group>
<Form.Button onClick={() => {
deleteHistoryLogs().then();
}}>Clear History Logs</Form.Button>
<Divider />
<Header as='h3'>
}}>Clear History Logs</Form.Button>
<Divider />
<Header as='h3'>
Monitoring Settings
</Header>
<Form.Group widths={3}>
</Header>
<Form.Group widths={3}>
<Form.Input
label='Longest Response Time'
label='Maximum Response Time'
name='ChannelDisableThreshold'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.ChannelDisableThreshold}
type='number'
min='0'
placeholder='Unit in secondsWhen all operating channels are testedChannels will be automatically disabled if this time is exceeded'
placeholder='Unit in seconds. When all operating channels are tested, channels will be automatically disabled if this time is exceeded'
/>
<Form.Input
label='Quota reminder threshold'
label='Quota Reminder Threshold'
name='QuotaRemindThreshold'
onChange={handleInputChange}
autoComplete='new-password'
@ -273,8 +273,8 @@ const OperationSetting = () => {
min='0'
placeholder='Email will be sent to remind users when the quota is below this'
/>
</Form.Group>
<Form.Group inline>
</Form.Group>
<Form.Group inline>
<Form.Checkbox
checked={inputs.AutomaticDisableChannelEnabled === 'true'}
label='Automatically disable the channel when it fails'
@ -283,12 +283,12 @@ const OperationSetting = () => {
/>
<Form.Checkbox
checked={inputs.AutomaticEnableChannelEnabled === 'true'}
label='成功时自动EnableChannel'
label='Automatically enable the channel when it succeeds'
name='AutomaticEnableChannelEnabled'
onChange={handleInputChange}
/>
</Form.Group>
<Form.Button onClick={() => {
</Form.Group>
<Form.Button onClick={() => {
submitConfig('monitor').then();
}}>Save Monitoring Settings</Form.Button>
<Divider />

View File

@ -249,7 +249,7 @@ const PersonalSetting = () => {
}
{
status.lark_client_id && (
<Button onClick={()=>{onLarkOAuthClicked(status.lark_client_id)}}>Bind飞书账号</Button>
<Button onClick={()=>{onLarkOAuthClicked(status.lark_client_id)}}>Bind Lark Account</Button>
)
}
<Button

View File

@ -264,7 +264,7 @@ const SystemSetting = () => {
<Form.Group widths='equal'>
<Form.Input
label='Server Address'
placeholder='For examplehttps://yourdomain.com'
placeholder='For example: https://yourdomain.com'
value={inputs.ServerAddress}
name='ServerAddress'
onChange={handleInputChange}
@ -290,7 +290,7 @@ const SystemSetting = () => {
size={'tiny'}
style={{ maxWidth: '450px' }}
>
<Modal.Header>警告</Modal.Header>
<Modal.Header>Warning</Modal.Header>
<Modal.Content>
<p>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?</p>
</Modal.Content>
@ -303,7 +303,7 @@ const SystemSetting = () => {
await updateOption('PasswordLoginEnabled', 'false');
}}
>
确定
Confirm
</Button>
</Modal.Actions>
</Modal>
@ -336,7 +336,7 @@ const SystemSetting = () => {
<Form.Group inline>
<Form.Checkbox
checked={inputs.RegisterEnabled === 'true'}
label='Allow new user registration (if this option is off, new users will not be able to register in any way'
label='Allow new user registration (if this option is off, new users will not be able to register in any way)'
name='RegisterEnabled'
onChange={handleInputChange}
/>
@ -349,12 +349,12 @@ const SystemSetting = () => {
</Form.Group>
<Divider />
<Header as='h3'>
配置邮箱域名白名单
<Header.Subheader>用以防止恶意Users利用临时邮箱批量Sign up</Header.Subheader>
Configure Email Domain Whitelist
<Header.Subheader>To prevent malicious users from using temporary emails to sign up in bulk</Header.Subheader>
</Header>
<Form.Group widths={3}>
<Form.Checkbox
label='Enable邮箱域名白名单'
label='Enable Email Domain Whitelist'
name='EmailDomainRestrictionEnabled'
onChange={handleInputChange}
checked={inputs.EmailDomainRestrictionEnabled === 'true'}
@ -362,8 +362,8 @@ const SystemSetting = () => {
</Form.Group>
<Form.Group widths={2}>
<Form.Dropdown
label='允许的邮箱域名'
placeholder='允许的邮箱域名'
label='Allowed Email Domains'
placeholder='Allowed Email Domains'
name='EmailDomainWhitelist'
required
fluid
@ -375,11 +375,11 @@ const SystemSetting = () => {
options={EmailDomainWhitelist}
/>
<Form.Input
label='添加新的允许的邮箱域名'
label='Add New Allowed Email Domain'
action={
<Button type='button' onClick={() => {
submitNewRestrictedDomain();
}}>填入</Button>
}}>Add</Button>
}
onKeyDown={(e) => {
if (e.key === 'Enter') {
@ -387,14 +387,14 @@ const SystemSetting = () => {
}
}}
autoComplete='new-password'
placeholder='Enter新的允许的邮箱域名'
placeholder='Enter new allowed email domain'
value={restrictedDomainInput}
onChange={(e, { value }) => {
setRestrictedDomainInput(value);
}}
/>
</Form.Group>
<Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单Settings</Form.Button>
<Form.Button onClick={submitEmailDomainWhitelist}>Save Email Domain Whitelist Settings</Form.Button>
<Divider />
<Header as='h3'>
Configure SMTP
@ -428,7 +428,7 @@ const SystemSetting = () => {
</Form.Group>
<Form.Group widths={3}>
<Form.Input
label='SMTP Sender email'
label='SMTP Sender Email'
name='SMTPFrom'
onChange={handleInputChange}
autoComplete='new-password'
@ -450,7 +450,7 @@ const SystemSetting = () => {
<Header as='h3'>
Configure GitHub OAuth App
<Header.Subheader>
To support login & registration via GitHub
To support login & registration via GitHub,
<a href='https://github.com/settings/developers' target='_blank'>
Click here
</a>
@ -459,7 +459,7 @@ const SystemSetting = () => {
</Header>
<Message>
Fill in the Homepage URL <code>{inputs.ServerAddress}</code>
Fill in the Authorization callback URL{' '}
, Fill in the Authorization callback URL{' '}
<code>{`${inputs.ServerAddress}/oauth/github`}</code>
</Message>
<Form.Group widths={3}>
@ -486,18 +486,18 @@ const SystemSetting = () => {
</Form.Button>
<Divider />
<Header as='h3'>
配置飞书授权Log in
Configure Lark OAuth
<Header.Subheader>
用以支持通过飞书进行Log inSign up
To support login & registration via Lark,
<a href='https://open.feishu.cn/app' target='_blank'>
Click here
</a>
Management你的飞书应用
Manage your Lark App
</Header.Subheader>
</Header>
<Message>
主页链接填 <code>{inputs.ServerAddress}</code>
重定向 URL {' '}
Fill in the Homepage URL <code>{inputs.ServerAddress}</code>
, Fill in the Redirect URL{' '}
<code>{`${inputs.ServerAddress}/oauth/lark`}</code>
</Message>
<Form.Group widths={3}>
@ -520,13 +520,13 @@ const SystemSetting = () => {
/>
</Form.Group>
<Form.Button onClick={submitLarkOAuth}>
保存飞书 OAuth Settings
Save Lark OAuth Settings
</Form.Button>
<Divider />
<Header as='h3'>
Configure WeChat Server
<Header.Subheader>
To support login & registration via WeChat
To support login & registration via WeChat,
<a
href='https://github.com/songquanpeng/wechat-server'
target='_blank'
@ -538,9 +538,9 @@ const SystemSetting = () => {
</Header>
<Form.Group widths={3}>
<Form.Input
label='WeChat Server Server Address'
label='WeChat Server Address'
name='WeChatServerAddress'
placeholder='For examplehttps://yourdomain.com'
placeholder='For example: https://yourdomain.com'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.WeChatServerAddress}
@ -568,29 +568,29 @@ const SystemSetting = () => {
</Form.Button>
<Divider />
<Header as='h3'>
配置 Message Pusher
Configure Message Pusher
<Header.Subheader>
用以推送报警信息
To push alert messages,
<a
href='https://github.com/songquanpeng/message-pusher'
target='_blank'
>
Click here
</a>
了解 Message Pusher
Learn about Message Pusher
</Header.Subheader>
</Header>
<Form.Group widths={3}>
<Form.Input
label='Message Pusher 推送地址'
label='Message Pusher Address'
name='MessagePusherAddress'
placeholder='For examplehttps://msgpusher.com/push/your_username'
placeholder='For example: https://msgpusher.com/push/your_username'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.MessagePusherAddress}
/>
<Form.Input
label='Message Pusher 访问凭证'
label='Message Pusher Access Credential'
name='MessagePusherToken'
type='password'
onChange={handleInputChange}
@ -600,13 +600,13 @@ const SystemSetting = () => {
/>
</Form.Group>
<Form.Button onClick={submitMessagePusher}>
保存 Message Pusher Settings
Save Message Pusher Settings
</Form.Button>
<Divider />
<Header as='h3'>
Configure Turnstile
<Header.Subheader>
To support user verification
To support user verification,
<a href='https://dash.cloudflare.com/' target='_blank'>
Click here
</a>

View File

@ -422,12 +422,12 @@ const TokensTable = () => {
</Button>
<Button size='small' onClick={refresh} loading={loading}>Refresh</Button>
<Dropdown
placeholder='排序方式'
placeholder='Sort By'
selection
options={[
{ key: '', text: 'Default排序', value: '' },
{ key: 'remain_quota', text: '按Remaining quota排序', value: 'remain_quota' },
{ key: 'used_quota', text: '按Used quota排序', value: 'used_quota' },
{ key: '', text: 'Default Order', value: '' },
{ key: 'remain_quota', text: 'Sort by Remaining Quota', value: 'remain_quota' },
{ key: 'used_quota', text: 'Sort by Used Quota', value: 'used_quota' },
]}
value={orderBy}
onChange={handleOrderByChange}

View File

@ -330,13 +330,13 @@ const UsersTable = () => {
Add New User
</Button>
<Dropdown
placeholder='排序方式'
placeholder='Sort By'
selection
options={[
{ key: '', text: 'Default排序', value: '' },
{ key: 'quota', text: '按Remaining quota排序', value: 'quota' },
{ key: 'used_quota', text: '按Used quota排序', value: 'used_quota' },
{ key: 'request_count', text: '按Number of Requests排序', value: 'request_count' },
{ key: '', text: 'Default Order', value: '' },
{ key: 'quota', text: 'Sort by Remaining Quota', value: 'quota' },
{ key: 'used_quota', text: 'Sort by Used Quota', value: 'used_quota' },
{ key: 'request_count', text: 'Sort by Number of Requests', value: 'request_count' },
]}
value={orderBy}
onChange={handleOrderByChange}

View File

@ -138,8 +138,8 @@ const EditToken = () => {
</Form.Field>
<Form.Field>
<Form.Dropdown
label='Model范围'
placeholder={'请选择允许使用的Model留空则不进行限制'}
label='Model Range'
placeholder={'Please select the allowed models, leave blank for no restriction'}
name='models'
fluid
multiple
@ -156,9 +156,9 @@ const EditToken = () => {
</Form.Field>
<Form.Field>
<Form.Input
label='IP 限制'
label='IP Restriction'
name='subnet'
placeholder={'请Enter允许访问的网段For example192.168.0.0/24请使用英文逗号分隔多个网段'}
placeholder={'Please enter the allowed subnet, e.g., 192.168.0.0/24, use commas to separate multiple subnets'}
onChange={handleInputChange}
value={inputs.subnet}
autoComplete='new-password'
@ -166,9 +166,9 @@ const EditToken = () => {
</Form.Field>
<Form.Field>
<Form.Input
label='Expiration time'
label='Expiration Time'
name='expired_time'
placeholder={'Please enter the expiration time, the format is yyyy-MM-dd HH:mm:ss, -1 means unlimited'}
placeholder={'Please enter the expiration time, format: yyyy-MM-dd HH:mm:ss, -1 means unlimited'}
onChange={handleInputChange}
value={expired_time}
autoComplete='new-password'
@ -192,7 +192,7 @@ const EditToken = () => {
setExpiredTime(0, 0, 0, 1);
}}>Expires after one minute</Button>
</div>
<Message>Note that the quota of the token is only used to limit the maximum quota usage of the token itself, and the actual usage is limited by the remaining quota of the account.</Message>
<Message>Note that the token's quota is only used to limit the maximum usage of the token itself, and the actual usage is limited by the remaining quota of the account.</Message>
<Form.Field>
<Form.Input
label={`Quota${renderQuotaWithPrompt(remain_quota)}`}