mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-09-17 17:16:38 +08:00
refactor: update UI text and error messages to English for better accessibility
This commit is contained in:
parent
76ba80d406
commit
13b1b165bd
@ -16,7 +16,7 @@ WORKDIR /web/air
|
||||
RUN npm install
|
||||
RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat ../VERSION) npm run build
|
||||
|
||||
FROM golang:1.23.3-bullseye AS builder2
|
||||
FROM golang:1.23.5-bullseye AS builder2
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y --no-install-recommends g++ make gcc git build-essential ca-certificates \
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
|
||||
func LogQuota(quota int64) string {
|
||||
if config.DisplayInCurrencyEnabled {
|
||||
return fmt.Sprintf("$%.6f 额度", float64(quota)/config.QuotaPerUnit)
|
||||
return fmt.Sprintf("$%.6f quota", float64(quota)/config.QuotaPerUnit)
|
||||
} else {
|
||||
return fmt.Sprintf("%d 点额度", quota)
|
||||
return fmt.Sprintf("%d point quota", quota)
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ type GitHubUser struct {
|
||||
|
||||
func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
|
||||
if code == "" {
|
||||
return nil, errors.New("无效的参数")
|
||||
return nil, errors.New("Invalid parameter")
|
||||
}
|
||||
values := map[string]string{"client_id": config.GitHubClientId, "client_secret": config.GitHubClientSecret, "code": code}
|
||||
jsonData, err := json.Marshal(values)
|
||||
@ -50,7 +50,7 @@ func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.SysLog(err.Error())
|
||||
return nil, errors.New("无法连接至 GitHub 服务器,请稍后重试!")
|
||||
return nil, errors.New("Unable to connect to GitHub server, please try again later!")
|
||||
}
|
||||
defer res.Body.Close()
|
||||
var oAuthResponse GitHubOAuthResponse
|
||||
@ -66,7 +66,7 @@ func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
|
||||
res2, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.SysLog(err.Error())
|
||||
return nil, errors.New("无法连接至 GitHub 服务器,请稍后重试!")
|
||||
return nil, errors.New("Unable to connect to GitHub server, please try again later!")
|
||||
}
|
||||
defer res2.Body.Close()
|
||||
var githubUser GitHubUser
|
||||
@ -75,7 +75,7 @@ func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
|
||||
return nil, err
|
||||
}
|
||||
if githubUser.Login == "" {
|
||||
return nil, errors.New("返回值非法,用户字段为空,请稍后重试!")
|
||||
return nil, errors.New("The return value is illegal, the user field is empty, please try again later!")
|
||||
}
|
||||
return &githubUser, nil
|
||||
}
|
||||
@ -99,7 +99,7 @@ func GitHubOAuth(c *gin.Context) {
|
||||
if !config.GitHubOAuthEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员未开启通过 GitHub 登录以及注册",
|
||||
"message": "The administrator did not turn on login and registration via GitHub",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -146,7 +146,7 @@ func GitHubOAuth(c *gin.Context) {
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员关闭了新用户注册",
|
||||
"message": "The administrator has turned off new user registration",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -154,7 +154,7 @@ func GitHubOAuth(c *gin.Context) {
|
||||
|
||||
if user.Status != model.UserStatusEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "用户已被封禁",
|
||||
"message": "User has been banned",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -166,7 +166,7 @@ func GitHubBind(c *gin.Context) {
|
||||
if !config.GitHubOAuthEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员未开启通过 GitHub 登录以及注册",
|
||||
"message": "The administrator did not turn on login and registration via GitHub",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -185,7 +185,7 @@ func GitHubBind(c *gin.Context) {
|
||||
if model.IsGitHubIdAlreadyTaken(user.GitHubId) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该 GitHub 账户已被绑定",
|
||||
"message": "The GitHub account has been bound",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ type LarkUser struct {
|
||||
|
||||
func getLarkUserInfoByCode(code string) (*LarkUser, error) {
|
||||
if code == "" {
|
||||
return nil, errors.New("无效的参数")
|
||||
return nil, errors.New("Invalid parameter")
|
||||
}
|
||||
values := map[string]string{
|
||||
"client_id": config.LarkClientId,
|
||||
@ -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("无法连接至飞书服务器,请稍后重试!")
|
||||
return nil, errors.New("None法连接至飞书服务器,请稍后Retry!")
|
||||
}
|
||||
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("无法连接至飞书服务器,请稍后重试!")
|
||||
return nil, errors.New("None法连接至飞书服务器,请稍后Retry!")
|
||||
}
|
||||
var larkUser LarkUser
|
||||
err = json.NewDecoder(res2.Body).Decode(&larkUser)
|
||||
@ -136,7 +136,7 @@ func LarkOAuth(c *gin.Context) {
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员关闭了新用户注册",
|
||||
"message": "The administrator has turned off new user registration",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -144,7 +144,7 @@ func LarkOAuth(c *gin.Context) {
|
||||
|
||||
if user.Status != model.UserStatusEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "用户已被封禁",
|
||||
"message": "User has been banned",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -168,7 +168,7 @@ func LarkBind(c *gin.Context) {
|
||||
if model.IsLarkIdAlreadyTaken(user.LarkId) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该飞书账户已被绑定",
|
||||
"message": "该飞书账户已被Bind",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ type OidcUser struct {
|
||||
|
||||
func getOidcUserInfoByCode(code string) (*OidcUser, error) {
|
||||
if code == "" {
|
||||
return nil, errors.New("无效的参数")
|
||||
return nil, errors.New("Invalid parameter")
|
||||
}
|
||||
values := map[string]string{
|
||||
"client_id": config.OidcClientId,
|
||||
@ -60,7 +60,7 @@ func getOidcUserInfoByCode(code string) (*OidcUser, error) {
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.SysLog(err.Error())
|
||||
return nil, errors.New("无法连接至 OIDC 服务器,请稍后重试!")
|
||||
return nil, errors.New("None法连接至 OIDC 服务器,请稍后Retry!")
|
||||
}
|
||||
defer res.Body.Close()
|
||||
var oidcResponse OidcResponse
|
||||
@ -76,7 +76,7 @@ func getOidcUserInfoByCode(code string) (*OidcUser, error) {
|
||||
res2, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.SysLog(err.Error())
|
||||
return nil, errors.New("无法连接至 OIDC 服务器,请稍后重试!")
|
||||
return nil, errors.New("None法连接至 OIDC 服务器,请稍后Retry!")
|
||||
}
|
||||
var oidcUser OidcUser
|
||||
err = json.NewDecoder(res2.Body).Decode(&oidcUser)
|
||||
@ -104,7 +104,7 @@ func OidcAuth(c *gin.Context) {
|
||||
if !config.OidcEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员未开启通过 OIDC 登录以及注册",
|
||||
"message": "Administrator未开启通过 OIDC Log in以及Sign up",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -153,7 +153,7 @@ func OidcAuth(c *gin.Context) {
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员关闭了新用户注册",
|
||||
"message": "The administrator has turned off new user registration",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -161,7 +161,7 @@ func OidcAuth(c *gin.Context) {
|
||||
|
||||
if user.Status != model.UserStatusEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "用户已被封禁",
|
||||
"message": "User has been banned",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -173,7 +173,7 @@ func OidcBind(c *gin.Context) {
|
||||
if !config.OidcEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员未开启通过 OIDC 登录以及注册",
|
||||
"message": "Administrator未开启通过 OIDC Log in以及Sign up",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -192,7 +192,7 @@ func OidcBind(c *gin.Context) {
|
||||
if model.IsOidcIdAlreadyTaken(user.OidcId) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该 OIDC 账户已被绑定",
|
||||
"message": "该 OIDC 账户已被Bind",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ type wechatLoginResponse struct {
|
||||
|
||||
func getWeChatIdByCode(code string) (string, error) {
|
||||
if code == "" {
|
||||
return "", errors.New("无效的参数")
|
||||
return "", errors.New("Invalid parameter")
|
||||
}
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/wechat/user?code=%s", config.WeChatServerAddress, code), nil)
|
||||
if err != nil {
|
||||
@ -47,7 +47,7 @@ func getWeChatIdByCode(code string) (string, error) {
|
||||
return "", errors.New(res.Message)
|
||||
}
|
||||
if res.Data == "" {
|
||||
return "", errors.New("验证码错误或已过期")
|
||||
return "", errors.New("Verification code error or expired")
|
||||
}
|
||||
return res.Data, nil
|
||||
}
|
||||
@ -55,7 +55,7 @@ func getWeChatIdByCode(code string) (string, error) {
|
||||
func WeChatAuth(c *gin.Context) {
|
||||
if !config.WeChatAuthEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "管理员未开启通过微信登录以及注册",
|
||||
"message": "The administrator has not enabled login and registration via WeChat",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -98,7 +98,7 @@ func WeChatAuth(c *gin.Context) {
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员关闭了新用户注册",
|
||||
"message": "The administrator has turned off new user registration",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -106,7 +106,7 @@ func WeChatAuth(c *gin.Context) {
|
||||
|
||||
if user.Status != model.UserStatusEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "用户已被封禁",
|
||||
"message": "User has been banned",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -117,7 +117,7 @@ func WeChatAuth(c *gin.Context) {
|
||||
func WeChatBind(c *gin.Context) {
|
||||
if !config.WeChatAuthEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "管理员未开启通过微信登录以及注册",
|
||||
"message": "The administrator has not enabled login and registration via WeChat",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -134,7 +134,7 @@ func WeChatBind(c *gin.Context) {
|
||||
if model.IsWeChatIdAlreadyTaken(wechatId) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该微信账号已被绑定",
|
||||
"message": "The WeChat account has been bound",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -295,7 +295,7 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
|
||||
baseURL = channel.GetBaseURL()
|
||||
}
|
||||
case channeltype.Azure:
|
||||
return 0, errors.New("尚未实现")
|
||||
return 0, errors.New("Not yet implemented")
|
||||
case channeltype.Custom:
|
||||
baseURL = channel.GetBaseURL()
|
||||
case channeltype.CloseAI:
|
||||
@ -313,7 +313,7 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
|
||||
case channeltype.DeepSeek:
|
||||
return updateChannelDeepSeekBalance(channel)
|
||||
default:
|
||||
return 0, errors.New("尚未实现")
|
||||
return 0, errors.New("Not yet implemented")
|
||||
}
|
||||
url := fmt.Sprintf("%s/v1/dashboard/billing/subscription", baseURL)
|
||||
|
||||
@ -399,7 +399,7 @@ func updateAllChannelsBalance() error {
|
||||
} else {
|
||||
// err is nil & balance <= 0 means quota is used up
|
||||
if balance <= 0 {
|
||||
monitor.DisableChannel(channel.Id, channel.Name, "余额不足")
|
||||
monitor.DisableChannel(channel.Id, channel.Name, "Insufficient balance")
|
||||
}
|
||||
}
|
||||
time.Sleep(config.RequestInterval)
|
||||
|
@ -173,7 +173,7 @@ func testChannels(notify bool, scope string) error {
|
||||
testAllChannelsLock.Lock()
|
||||
if testAllChannelsRunning {
|
||||
testAllChannelsLock.Unlock()
|
||||
return errors.New("测试已在运行中")
|
||||
return errors.New("Test is already running")
|
||||
}
|
||||
testAllChannelsRunning = true
|
||||
testAllChannelsLock.Unlock()
|
||||
@ -194,11 +194,11 @@ func testChannels(notify bool, scope string) error {
|
||||
tok := time.Now()
|
||||
milliseconds := tok.Sub(tik).Milliseconds()
|
||||
if isChannelEnabled && milliseconds > disableThreshold {
|
||||
err = fmt.Errorf("响应时间 %.2fs 超过阈值 %.2fs", float64(milliseconds)/1000.0, float64(disableThreshold)/1000.0)
|
||||
err = fmt.Errorf("Response time %.2fs exceeds threshold %.2fs", float64(milliseconds)/1000.0, float64(disableThreshold)/1000.0)
|
||||
if config.AutomaticDisableChannelEnabled {
|
||||
monitor.DisableChannel(channel.Id, channel.Name, err.Error())
|
||||
} else {
|
||||
_ = message.Notify(message.ByAll, fmt.Sprintf("渠道 %s (%d)测试超时", channel.Name, channel.Id), "", err.Error())
|
||||
_ = message.Notify(message.ByAll, fmt.Sprintf("Channel %s (%d)Test超时", channel.Name, channel.Id), "", err.Error())
|
||||
}
|
||||
}
|
||||
if isChannelEnabled && monitor.ShouldDisableChannel(openaiErr, -1) {
|
||||
@ -214,7 +214,7 @@ func testChannels(notify bool, scope string) error {
|
||||
testAllChannelsRunning = false
|
||||
testAllChannelsLock.Unlock()
|
||||
if notify {
|
||||
err := message.Notify(message.ByAll, "渠道测试完成", "", "渠道测试完成,如果没有收到禁用通知,说明所有渠道都正常")
|
||||
err := message.Notify(message.ByAll, "Channel test completed", "", "Channel test completed, if you have not received the disable notification, it means that all channels are normal")
|
||||
if err != nil {
|
||||
logger.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ func SendEmailVerification(c *gin.Context) {
|
||||
if err := common.Validate.Var(email, "required,email"); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -100,7 +100,7 @@ func SendEmailVerification(c *gin.Context) {
|
||||
if !allowed {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员启用了邮箱域名白名单,您的邮箱地址的域名不在白名单中",
|
||||
"message": "AdministratorEnable了邮箱域名白名单,您的Email address的域名不在白名单中",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -108,16 +108,16 @@ func SendEmailVerification(c *gin.Context) {
|
||||
if model.IsEmailAlreadyTaken(email) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "邮箱地址已被占用",
|
||||
"message": "Email address is occupied",
|
||||
})
|
||||
return
|
||||
}
|
||||
code := common.GenerateVerificationCode(6)
|
||||
common.RegisterVerificationCodeWithKey(email, code, common.EmailVerificationPurpose)
|
||||
subject := fmt.Sprintf("%s邮箱验证邮件", config.SystemName)
|
||||
content := fmt.Sprintf("<p>您好,你正在进行%s邮箱验证。</p>"+
|
||||
"<p>您的验证码为: <strong>%s</strong></p>"+
|
||||
"<p>验证码 %d 分钟内有效,如果不是本人操作,请忽略。</p>", config.SystemName, code, common.VerificationValidMinutes)
|
||||
subject := fmt.Sprintf("%s Email verification email", config.SystemName)
|
||||
content := fmt.Sprintf("<p>Hello, you are verifying %s email.</p>"+
|
||||
"<p>Your verification code is: <strong>%s</strong></p>"+
|
||||
"<p>The verification code is valid within %d minutes. If it is not your operation, please ignore it.</p>", config.SystemName, code, common.VerificationValidMinutes)
|
||||
err := message.SendEmail(subject, email, content)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@ -138,25 +138,25 @@ func SendPasswordResetEmail(c *gin.Context) {
|
||||
if err := common.Validate.Var(email, "required,email"); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
if !model.IsEmailAlreadyTaken(email) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该邮箱地址未注册",
|
||||
"message": "The email address is not registered",
|
||||
})
|
||||
return
|
||||
}
|
||||
code := common.GenerateVerificationCode(0)
|
||||
common.RegisterVerificationCodeWithKey(email, code, common.PasswordResetPurpose)
|
||||
link := fmt.Sprintf("%s/user/reset?email=%s&token=%s", config.ServerAddress, email, code)
|
||||
subject := fmt.Sprintf("%s密码重置", config.SystemName)
|
||||
content := fmt.Sprintf("<p>您好,你正在进行%s密码重置。</p>"+
|
||||
"<p>点击 <a href='%s'>此处</a> 进行密码重置。</p>"+
|
||||
"<p>如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开:<br> %s </p>"+
|
||||
"<p>重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>", config.SystemName, link, link, common.VerificationValidMinutes)
|
||||
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>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 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@ -183,14 +183,14 @@ func ResetPassword(c *gin.Context) {
|
||||
if req.Email == "" || req.Token == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
if !common.VerifyCodeWithKey(req.Email, req.Token, common.PasswordResetPurpose) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "重置链接非法或已过期",
|
||||
"message": "Reset link is illegal or expired",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func UpdateOption(c *gin.Context) {
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -47,7 +47,7 @@ func UpdateOption(c *gin.Context) {
|
||||
if !config.ValidThemes[option.Value] {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的主题",
|
||||
"message": "None效的主题",
|
||||
})
|
||||
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": "无法启用 GitHub OAuth,请先填入 GitHub Client Id 以及 GitHub Client Secret!",
|
||||
"message": "None法Enable GitHub OAuth,请先填入 GitHub Client Id 以及 GitHub Client Secret!",
|
||||
})
|
||||
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": "无法启用邮箱域名限制,请先填入限制的邮箱域名!",
|
||||
"message": "None法Enable邮箱域名限制,请先填入限制的邮箱域名!",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -71,7 +71,7 @@ func UpdateOption(c *gin.Context) {
|
||||
if option.Value == "true" && config.WeChatServerAddress == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法启用微信登录,请先填入微信登录相关配置信息!",
|
||||
"message": "Unable to enable WeChat login, please fill in the relevant configuration information for WeChat login first!",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -79,7 +79,7 @@ func UpdateOption(c *gin.Context) {
|
||||
if option.Value == "true" && config.TurnstileSiteKey == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法启用 Turnstile 校验,请先填入 Turnstile 校验相关配置信息!",
|
||||
"message": "Unable to enable Turnstile verification, please fill in the relevant configuration information for Turnstile verification first!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -89,21 +89,21 @@ func AddRedemption(c *gin.Context) {
|
||||
if len(redemption.Name) == 0 || len(redemption.Name) > 20 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "兑换码名称长度必须在1-20之间",
|
||||
"message": "The length of the redemption code name must be between 1-20",
|
||||
})
|
||||
return
|
||||
}
|
||||
if redemption.Count <= 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "兑换码个数必须大于0",
|
||||
"message": "The number of redemption codes must be greater than 0",
|
||||
})
|
||||
return
|
||||
}
|
||||
if redemption.Count > 100 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "一次兑换码批量生成的个数不能大于 100",
|
||||
"message": "The number of redemption codes generated in a batch cannot be greater than 100",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ func Relay(c *gin.Context) {
|
||||
|
||||
if bizErr != nil {
|
||||
if bizErr.StatusCode == http.StatusTooManyRequests {
|
||||
bizErr.Error.Message = "当前分组上游负载已饱和,请稍后再试"
|
||||
bizErr.Error.Message = "The current group load is saturated, please try again later"
|
||||
}
|
||||
|
||||
// BUG: bizErr is in race condition
|
||||
|
@ -134,13 +134,13 @@ func GetTokenStatus(c *gin.Context) {
|
||||
|
||||
func validateToken(c *gin.Context, token *model.Token) error {
|
||||
if len(token.Name) > 30 {
|
||||
return fmt.Errorf("令牌名称过长")
|
||||
return fmt.Errorf("Token name is too long")
|
||||
}
|
||||
|
||||
if token.Subnet != nil && *token.Subnet != "" {
|
||||
err := network.IsValidSubnets(*token.Subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无效的网段:%s", err.Error())
|
||||
return fmt.Errorf("None效的网段:%s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ func AddToken(c *gin.Context) {
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": fmt.Sprintf("参数错误:%s", err.Error()),
|
||||
"message": fmt.Sprintf("参数Error:%s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -247,7 +247,7 @@ func ConsumeToken(c *gin.Context) {
|
||||
if cleanToken.Status != model.TokenStatusEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌未启用",
|
||||
"message": "API KeysNot enabled",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -256,7 +256,7 @@ func ConsumeToken(c *gin.Context) {
|
||||
cleanToken.ExpiredTime <= helper.GetTimestamp() && cleanToken.ExpiredTime != -1 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期",
|
||||
"message": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -264,7 +264,7 @@ func ConsumeToken(c *gin.Context) {
|
||||
cleanToken.RemainQuota <= 0 && !cleanToken.UnlimitedQuota {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度",
|
||||
"message": "The available quota of the token has been used up and cannot be enabled. Please modify the remaining quota of the token, or set it to unlimited quota",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -274,7 +274,7 @@ func ConsumeToken(c *gin.Context) {
|
||||
if cleanToken.RemainQuota < int64(tokenPatch.AddUsedQuota) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "剩余额度不足",
|
||||
"message": "Remaining quota不足",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -336,7 +336,7 @@ func UpdateToken(c *gin.Context) {
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": fmt.Sprintf("参数错误:%s", err.Error()),
|
||||
"message": fmt.Sprintf("参数Error:%s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -357,7 +357,7 @@ func UpdateToken(c *gin.Context) {
|
||||
token.ExpiredTime != -1 && token.ExpiredTime < helper.GetTimestamp() {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期",
|
||||
"message": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -366,7 +366,7 @@ func UpdateToken(c *gin.Context) {
|
||||
token.RemainQuota <= 0 && !token.UnlimitedQuota {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度",
|
||||
"message": "The available quota of the token has been used up and cannot be enabled. Please modify the remaining quota of the token, or set it to unlimited quota",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ type LoginRequest struct {
|
||||
func Login(c *gin.Context) {
|
||||
if !config.PasswordLoginEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "管理员关闭了密码登录",
|
||||
"message": "The administrator has turned off password login",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -33,7 +33,7 @@ func Login(c *gin.Context) {
|
||||
err := json.NewDecoder(c.Request.Body).Decode(&loginRequest)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -42,7 +42,7 @@ func Login(c *gin.Context) {
|
||||
password := loginRequest.Password
|
||||
if username == "" || password == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -72,7 +72,7 @@ func SetupLogin(user *model.User, c *gin.Context) {
|
||||
err := session.Save()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "无法保存会话信息,请重试",
|
||||
"message": "Unable to save session information, please try again",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -117,14 +117,14 @@ func Logout(c *gin.Context) {
|
||||
func Register(c *gin.Context) {
|
||||
if !config.RegisterEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "管理员关闭了新用户注册",
|
||||
"message": "The administrator has turned off new user registration",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
}
|
||||
if !config.PasswordRegisterEnabled {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "管理员关闭了通过密码进行注册,请使用第三方账户验证的形式进行注册",
|
||||
"message": "The administrator has turned off registration via password. Please use the form of third-party account verification to register",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
@ -134,14 +134,14 @@ func Register(c *gin.Context) {
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err := common.Validate.Struct(&user); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "输入不合法 " + err.Error(),
|
||||
"message": "Input is illegal " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -149,14 +149,14 @@ func Register(c *gin.Context) {
|
||||
if user.Email == "" || user.VerificationCode == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "管理员开启了邮箱验证,请输入邮箱地址和验证码",
|
||||
"message": "The administrator has turned on email verification, please enter the email address and verification code",
|
||||
})
|
||||
return
|
||||
}
|
||||
if !common.VerifyCodeWithKey(user.Email, user.VerificationCode, common.EmailVerificationPurpose) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "验证码错误或已过期",
|
||||
"message": "Verification code error or expired",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -249,7 +249,7 @@ func GetUser(c *gin.Context) {
|
||||
if myRole <= user.Role && myRole != model.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无权获取同级或更高等级用户的信息",
|
||||
"message": "No permission to get information of users at the same level or higher",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -271,7 +271,7 @@ func GetUserDashboard(c *gin.Context) {
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法获取统计信息",
|
||||
"message": "None法获取Statistics",
|
||||
"data": nil,
|
||||
})
|
||||
return
|
||||
@ -299,7 +299,7 @@ func GenerateAccessToken(c *gin.Context) {
|
||||
if model.DB.Where("access_token = ?", user.AccessToken).First(user).RowsAffected != 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "请重试,系统生成的 UUID 竟然重复了!",
|
||||
"message": "Please try again, the system-generated UUID is actually duplicated!",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -382,7 +382,7 @@ func UpdateUser(c *gin.Context) {
|
||||
if err != nil || updatedUser.Id == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -392,7 +392,7 @@ func UpdateUser(c *gin.Context) {
|
||||
if err := common.Validate.Struct(&updatedUser); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "输入不合法 " + err.Error(),
|
||||
"message": "Input is illegal " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -408,14 +408,14 @@ func UpdateUser(c *gin.Context) {
|
||||
if myRole <= originUser.Role && myRole != model.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无权更新同权限等级或更高权限等级的用户信息",
|
||||
"message": "No permission to update user information with the same permission level or higher permission level",
|
||||
})
|
||||
return
|
||||
}
|
||||
if myRole <= updatedUser.Role && myRole != model.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无权将其他用户权限等级提升到大于等于自己的权限等级",
|
||||
"message": "None权将其他Users权限等级Promote到大于Equals自己的权限等级",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -431,7 +431,7 @@ func UpdateUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if originUser.Quota != updatedUser.Quota {
|
||||
model.RecordLog(originUser.Id, model.LogTypeManage, fmt.Sprintf("管理员将用户额度从 %s修改为 %s", common.LogQuota(originUser.Quota), common.LogQuota(updatedUser.Quota)))
|
||||
model.RecordLog(originUser.Id, model.LogTypeManage, fmt.Sprintf("The administrator changed the user quota from %s to %s", common.LogQuota(originUser.Quota), common.LogQuota(updatedUser.Quota)))
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
@ -446,7 +446,7 @@ func UpdateSelf(c *gin.Context) {
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -456,7 +456,7 @@ func UpdateSelf(c *gin.Context) {
|
||||
if err := common.Validate.Struct(&user); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "输入不合法 " + err.Error(),
|
||||
"message": "Input is illegal " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -508,7 +508,7 @@ func DeleteUser(c *gin.Context) {
|
||||
if myRole <= originUser.Role {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无权删除同权限等级或更高权限等级的用户",
|
||||
"message": "No permission to delete users with the same permission level or higher permission level",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -529,7 +529,7 @@ func DeleteSelf(c *gin.Context) {
|
||||
if user.Role == model.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "不能删除超级管理员账户",
|
||||
"message": "不能DeleteSuper administrator账户",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -555,14 +555,14 @@ func CreateUser(c *gin.Context) {
|
||||
if err != nil || user.Username == "" || user.Password == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err := common.Validate.Struct(&user); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "输入不合法 " + err.Error(),
|
||||
"message": "Input is illegal " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -573,7 +573,7 @@ func CreateUser(c *gin.Context) {
|
||||
if user.Role >= myRole {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法创建权限大于等于自己的用户",
|
||||
"message": "Unable to create users with permissions greater than or equal to your own",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -611,7 +611,7 @@ func ManageUser(c *gin.Context) {
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无效的参数",
|
||||
"message": "Invalid parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -623,7 +623,7 @@ func ManageUser(c *gin.Context) {
|
||||
if user.Id == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "用户不存在",
|
||||
"message": "User does not exist",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -631,7 +631,7 @@ func ManageUser(c *gin.Context) {
|
||||
if myRole <= user.Role && myRole != model.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无权更新同权限等级或更高权限等级的用户信息",
|
||||
"message": "No permission to update user information with the same permission level or higher permission level",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -641,7 +641,7 @@ func ManageUser(c *gin.Context) {
|
||||
if user.Role == model.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法禁用超级管理员用户",
|
||||
"message": "Unable to disable super administrator user",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -651,7 +651,7 @@ func ManageUser(c *gin.Context) {
|
||||
if user.Role == model.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法删除超级管理员用户",
|
||||
"message": "Unable to delete super administrator user",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -666,14 +666,14 @@ func ManageUser(c *gin.Context) {
|
||||
if myRole != model.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "普通管理员用户无法提升其他用户为管理员",
|
||||
"message": "Ordinary administrator users cannot promote other users to administrators",
|
||||
})
|
||||
return
|
||||
}
|
||||
if user.Role >= model.RoleAdminUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该用户已经是管理员",
|
||||
"message": "The user is already an administrator",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -682,14 +682,14 @@ func ManageUser(c *gin.Context) {
|
||||
if user.Role == model.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法降级超级管理员用户",
|
||||
"message": "Unable to downgrade super administrator user",
|
||||
})
|
||||
return
|
||||
}
|
||||
if user.Role == model.RoleCommonUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该用户已经是普通用户",
|
||||
"message": "The user is already an ordinary user",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -721,7 +721,7 @@ func EmailBind(c *gin.Context) {
|
||||
if !common.VerifyCodeWithKey(email, code, common.EmailVerificationPurpose) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "验证码错误或已过期",
|
||||
"message": "Verification code error or expired",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -813,7 +813,7 @@ func AdminTopUp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if req.Remark == "" {
|
||||
req.Remark = fmt.Sprintf("通过 API 充值 %s", common.LogQuota(int64(req.Quota)))
|
||||
req.Remark = fmt.Sprintf("通过 API Recharge %s", common.LogQuota(int64(req.Quota)))
|
||||
}
|
||||
model.RecordTopupLog(req.UserId, req.Remark, req.Quota)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
|
@ -27,7 +27,7 @@ func authHelper(c *gin.Context, minRole int) {
|
||||
if accessToken == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"success": false,
|
||||
"message": "无权进行此操作,未登录且未提供 access token",
|
||||
"message": "No permission to perform this operation, not logged in and no access token provided",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
@ -43,7 +43,7 @@ func authHelper(c *gin.Context, minRole int) {
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无权进行此操作,access token 无效",
|
||||
"message": "No permission to perform this operation, access token is invalid",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
@ -52,7 +52,7 @@ func authHelper(c *gin.Context, minRole int) {
|
||||
if status.(int) == model.UserStatusDisabled || blacklist.IsUserBanned(id.(int)) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "用户已被封禁",
|
||||
"message": "User has been banned",
|
||||
})
|
||||
session := sessions.Default(c)
|
||||
session.Clear()
|
||||
@ -63,7 +63,7 @@ func authHelper(c *gin.Context, minRole int) {
|
||||
if role.(int) < minRole {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无权进行此操作,权限不足",
|
||||
"message": "No permission to perform this operation, insufficient permissions",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
@ -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("该令牌只能在指定网段使用:%s,当前 ip:%s", *token.Subnet, c.ClientIP()))
|
||||
abortWithMessage(c, http.StatusForbidden, fmt.Sprintf("该API Keys只能在指定网段使用:%s,当前 ip:%s", *token.Subnet, c.ClientIP()))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -117,7 +117,7 @@ func TokenAuth() func(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if !userEnabled || blacklist.IsUserBanned(token.UserId) {
|
||||
abortWithMessage(c, http.StatusForbidden, "用户已被封禁")
|
||||
abortWithMessage(c, http.StatusForbidden, "User has been banned")
|
||||
return
|
||||
}
|
||||
requestModel, err := getRequestModel(c)
|
||||
@ -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("该令牌无权使用模型:%s", requestModel))
|
||||
abortWithMessage(c, http.StatusForbidden, fmt.Sprintf("该API KeysNone权使用Model:%s", requestModel))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -144,7 +144,7 @@ func TokenAuth() func(c *gin.Context) {
|
||||
if model.IsAdmin(token.UserId) {
|
||||
c.Set(ctxkey.SpecificChannelId, parts[1])
|
||||
} else {
|
||||
abortWithMessage(c, http.StatusForbidden, "普通用户不支持指定渠道")
|
||||
abortWithMessage(c, http.StatusForbidden, "Ordinary users do not support specifying channels")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -30,16 +30,16 @@ func Distribute() func(c *gin.Context) {
|
||||
if ok {
|
||||
id, err := strconv.Atoi(channelId.(string))
|
||||
if err != nil {
|
||||
abortWithMessage(c, http.StatusBadRequest, "无效的渠道 Id")
|
||||
abortWithMessage(c, http.StatusBadRequest, "None效的Channel Id")
|
||||
return
|
||||
}
|
||||
channel, err = model.GetChannelById(id, true)
|
||||
if err != nil {
|
||||
abortWithMessage(c, http.StatusBadRequest, "无效的渠道 Id")
|
||||
abortWithMessage(c, http.StatusBadRequest, "None效的Channel Id")
|
||||
return
|
||||
}
|
||||
if channel.Status != model.ChannelStatusEnabled {
|
||||
abortWithMessage(c, http.StatusForbidden, "该渠道已被禁用")
|
||||
abortWithMessage(c, http.StatusForbidden, "The channel has been disabled")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@ -47,10 +47,10 @@ func Distribute() func(c *gin.Context) {
|
||||
var err error
|
||||
channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, requestModel, false)
|
||||
if err != nil {
|
||||
message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", userGroup, requestModel)
|
||||
message := fmt.Sprintf("当前Group %s 下对于Model %s No available channels", userGroup, requestModel)
|
||||
if channel != nil {
|
||||
logger.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
|
||||
message = "数据库一致性已被破坏,请联系管理员"
|
||||
logger.SysError(fmt.Sprintf("Channel does not exist: %d", channel.Id))
|
||||
message = "Database consistency has been broken, please contact the administrator"
|
||||
}
|
||||
abortWithMessage(c, http.StatusServiceUnavailable, message)
|
||||
return
|
||||
|
@ -27,7 +27,7 @@ func TurnstileCheck() gin.HandlerFunc {
|
||||
if response == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "Turnstile token 为空",
|
||||
"message": "Turnstile token is empty",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
@ -61,7 +61,7 @@ func TurnstileCheck() gin.HandlerFunc {
|
||||
if !res.Success {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "Turnstile 校验失败,请刷新重试!",
|
||||
"message": "Turnstile verification failed, please refresh and try again!",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
@ -70,7 +70,7 @@ func TurnstileCheck() gin.HandlerFunc {
|
||||
err = session.Save()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "无法保存会话信息,请重试",
|
||||
"message": "Unable to save session information, please try again",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
|
@ -40,7 +40,7 @@ func SearchRedemptions(keyword string) (redemptions []*Redemption, err error) {
|
||||
|
||||
func GetRedemptionById(id int) (*Redemption, error) {
|
||||
if id == 0 {
|
||||
return nil, errors.New("id 为空!")
|
||||
return nil, errors.New("id is empty!")
|
||||
}
|
||||
redemption := Redemption{Id: id}
|
||||
var err error = nil
|
||||
@ -50,10 +50,10 @@ func GetRedemptionById(id int) (*Redemption, error) {
|
||||
|
||||
func Redeem(key string, userId int) (quota int64, err error) {
|
||||
if key == "" {
|
||||
return 0, errors.New("未提供兑换码")
|
||||
return 0, errors.New("No redemption code provided")
|
||||
}
|
||||
if userId == 0 {
|
||||
return 0, errors.New("无效的 user id")
|
||||
return 0, errors.New("Invalid user id")
|
||||
}
|
||||
redemption := &Redemption{}
|
||||
|
||||
@ -65,10 +65,10 @@ func Redeem(key string, userId int) (quota int64, err error) {
|
||||
err = DB.Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Set("gorm:query_option", "FOR UPDATE").Where(keyCol+" = ?", key).First(redemption).Error
|
||||
if err != nil {
|
||||
return errors.New("无效的兑换码")
|
||||
return errors.New("Invalid redemption code")
|
||||
}
|
||||
if redemption.Status != RedemptionCodeStatusEnabled {
|
||||
return errors.New("该兑换码已被使用")
|
||||
return errors.New("The redemption code has been used")
|
||||
}
|
||||
err = tx.Model(&User{}).Where("id = ?", userId).Update("quota", gorm.Expr("quota + ?", redemption.Quota)).Error
|
||||
if err != nil {
|
||||
@ -80,9 +80,9 @@ func Redeem(key string, userId int) (quota int64, err error) {
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return 0, errors.New("兑换失败," + err.Error())
|
||||
return 0, errors.New("Redeem失败," + err.Error())
|
||||
}
|
||||
RecordLog(userId, LogTypeTopup, fmt.Sprintf("通过兑换码充值 %s", common.LogQuota(redemption.Quota)))
|
||||
RecordLog(userId, LogTypeTopup, fmt.Sprintf("Recharge %s through redemption code", common.LogQuota(redemption.Quota)))
|
||||
return redemption.Quota, nil
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ func (redemption *Redemption) Delete() error {
|
||||
|
||||
func DeleteRedemptionById(id int) (err error) {
|
||||
if id == 0 {
|
||||
return errors.New("id 为空!")
|
||||
return errors.New("id is empty!")
|
||||
}
|
||||
redemption := Redemption{Id: id}
|
||||
err = DB.Where(redemption).First(&redemption).Error
|
||||
|
@ -60,7 +60,7 @@ func SearchUserTokens(userId int, keyword string) (tokens []*Token, err error) {
|
||||
|
||||
func ValidateUserToken(key string) (token *Token, err error) {
|
||||
if key == "" {
|
||||
return nil, errors.New("未提供令牌")
|
||||
return nil, errors.New("No token provided")
|
||||
}
|
||||
token, err = CacheGetTokenByKey(key)
|
||||
if err != nil {
|
||||
@ -72,12 +72,12 @@ 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("令牌 %s(#%d)额度已用尽", token.Name, token.Id)
|
||||
return nil, fmt.Errorf("API Keys %s(#%d)Quota已用尽", token.Name, token.Id)
|
||||
} else if token.Status == TokenStatusExpired {
|
||||
return nil, errors.New("该令牌已过期")
|
||||
return nil, errors.New("The token has expired")
|
||||
}
|
||||
if token.Status != TokenStatusEnabled {
|
||||
return nil, errors.New("该令牌状态不可用")
|
||||
return nil, errors.New("The token status is not available")
|
||||
}
|
||||
if token.ExpiredTime != -1 && token.ExpiredTime < helper.GetTimestamp() {
|
||||
if !common.RedisEnabled {
|
||||
@ -87,7 +87,7 @@ func ValidateUserToken(key string) (token *Token, err error) {
|
||||
logger.SysError("failed to update token status" + err.Error())
|
||||
}
|
||||
}
|
||||
return nil, errors.New("该令牌已过期")
|
||||
return nil, errors.New("The token has expired")
|
||||
}
|
||||
if !token.UnlimitedQuota && token.RemainQuota <= 0 {
|
||||
if !common.RedisEnabled {
|
||||
@ -98,14 +98,14 @@ func ValidateUserToken(key string) (token *Token, err error) {
|
||||
logger.SysError("failed to update token status" + err.Error())
|
||||
}
|
||||
}
|
||||
return nil, errors.New("该令牌额度已用尽")
|
||||
return nil, errors.New("The token quota has been used up")
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func GetTokenByIds(id int, userId int) (*Token, error) {
|
||||
if id == 0 || userId == 0 {
|
||||
return nil, errors.New("id 或 userId 为空!")
|
||||
return nil, errors.New("id or userId is empty!")
|
||||
}
|
||||
token := Token{Id: id, UserId: userId}
|
||||
var err error = nil
|
||||
@ -115,7 +115,7 @@ func GetTokenByIds(id int, userId int) (*Token, error) {
|
||||
|
||||
func GetTokenById(id int) (*Token, error) {
|
||||
if id == 0 {
|
||||
return nil, errors.New("id 为空!")
|
||||
return nil, errors.New("id is empty!")
|
||||
}
|
||||
token := Token{Id: id}
|
||||
var err error = nil
|
||||
@ -160,7 +160,7 @@ func (t *Token) GetModels() string {
|
||||
func DeleteTokenById(id int, userId int) (err error) {
|
||||
// Why we need userId here? In case user want to delete other's token.
|
||||
if id == 0 || userId == 0 {
|
||||
return errors.New("id 或 userId 为空!")
|
||||
return errors.New("id or userId is empty!")
|
||||
}
|
||||
token := Token{Id: id, UserId: userId}
|
||||
err = DB.Where(token).First(&token).Error
|
||||
@ -172,7 +172,7 @@ func DeleteTokenById(id int, userId int) (err error) {
|
||||
|
||||
func IncreaseTokenQuota(id int, quota int64) (err error) {
|
||||
if quota < 0 {
|
||||
return errors.New("quota 不能为负数!")
|
||||
return errors.New("quota cannot be negative!")
|
||||
}
|
||||
if config.BatchUpdateEnabled {
|
||||
addNewRecord(BatchUpdateTypeTokenQuota, id, quota)
|
||||
@ -194,7 +194,7 @@ func increaseTokenQuota(id int, quota int64) (err error) {
|
||||
|
||||
func DecreaseTokenQuota(id int, quota int64) (err error) {
|
||||
if quota < 0 {
|
||||
return errors.New("quota 不能为负数!")
|
||||
return errors.New("quota cannot be negative!")
|
||||
}
|
||||
if config.BatchUpdateEnabled {
|
||||
addNewRecord(BatchUpdateTypeTokenQuota, id, -quota)
|
||||
@ -216,21 +216,21 @@ func decreaseTokenQuota(id int, quota int64) (err error) {
|
||||
|
||||
func PreConsumeTokenQuota(tokenId int, quota int64) (err error) {
|
||||
if quota < 0 {
|
||||
return errors.New("quota 不能为负数!")
|
||||
return errors.New("quota cannot be negative!")
|
||||
}
|
||||
token, err := GetTokenById(tokenId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !token.UnlimitedQuota && token.RemainQuota < quota {
|
||||
return errors.New("令牌额度不足")
|
||||
return errors.New("Insufficient token quota")
|
||||
}
|
||||
userQuota, err := GetUserQuota(token.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if userQuota < quota {
|
||||
return errors.New("用户额度不足")
|
||||
return errors.New("Insufficient user quota")
|
||||
}
|
||||
quotaTooLow := userQuota >= config.QuotaRemindThreshold && userQuota-quota < config.QuotaRemindThreshold
|
||||
noMoreQuota := userQuota-quota <= 0
|
||||
@ -240,14 +240,14 @@ func PreConsumeTokenQuota(tokenId int, quota int64) (err error) {
|
||||
if err != nil {
|
||||
logger.SysError("failed to fetch user email: " + err.Error())
|
||||
}
|
||||
prompt := "您的额度即将用尽"
|
||||
prompt := "Your quota is about to run out"
|
||||
if noMoreQuota {
|
||||
prompt = "您的额度已用尽"
|
||||
prompt = "Your quota has been used up"
|
||||
}
|
||||
if email != "" {
|
||||
topUpLink := fmt.Sprintf("%s/topup", config.ServerAddress)
|
||||
err = message.SendEmail(prompt, email,
|
||||
fmt.Sprintf("%s,当前剩余额度为 %d,为了不影响您的使用,请及时充值。<br/>充值链接:<a href='%s'>%s</a>", prompt, userQuota, topUpLink, topUpLink))
|
||||
fmt.Sprintf("%s, the current remaining quota is %d, in order not to affect your use, please recharge in time. <br/> Recharge link: <a href='%s'>%s</a>", prompt, userQuota, topUpLink, topUpLink))
|
||||
if err != nil {
|
||||
logger.SysError("failed to send email" + err.Error())
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ func SearchUsers(keyword string) (users []*User, err error) {
|
||||
|
||||
func GetUserById(id int, selectAll bool) (*User, error) {
|
||||
if id == 0 {
|
||||
return nil, errors.New("id 为空!")
|
||||
return nil, errors.New("id is empty!")
|
||||
}
|
||||
user := User{Id: id}
|
||||
var err error = nil
|
||||
@ -100,7 +100,7 @@ func GetUserById(id int, selectAll bool) (*User, error) {
|
||||
|
||||
func GetUserIdByAffCode(affCode string) (int, error) {
|
||||
if affCode == "" {
|
||||
return 0, errors.New("affCode 为空!")
|
||||
return 0, errors.New("affCode is empty!")
|
||||
}
|
||||
var user User
|
||||
err := DB.Select("id").First(&user, "aff_code = ?", affCode).Error
|
||||
@ -109,7 +109,7 @@ func GetUserIdByAffCode(affCode string) (int, error) {
|
||||
|
||||
func DeleteUserById(id int) (err error) {
|
||||
if id == 0 {
|
||||
return errors.New("id 为空!")
|
||||
return errors.New("id is empty!")
|
||||
}
|
||||
user := User{Id: id}
|
||||
return user.Delete()
|
||||
@ -131,16 +131,16 @@ func (user *User) Insert(inviterId int) error {
|
||||
return result.Error
|
||||
}
|
||||
if config.QuotaForNewUser > 0 {
|
||||
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("新用户注册赠送 %s", common.LogQuota(config.QuotaForNewUser)))
|
||||
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("New user registration gives %s", common.LogQuota(config.QuotaForNewUser)))
|
||||
}
|
||||
if inviterId != 0 {
|
||||
if config.QuotaForInvitee > 0 {
|
||||
_ = IncreaseUserQuota(user.Id, config.QuotaForInvitee)
|
||||
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", common.LogQuota(config.QuotaForInvitee)))
|
||||
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("Use invitation code to give %s", common.LogQuota(config.QuotaForInvitee)))
|
||||
}
|
||||
if config.QuotaForInviter > 0 {
|
||||
_ = IncreaseUserQuota(inviterId, config.QuotaForInviter)
|
||||
RecordLog(inviterId, LogTypeSystem, fmt.Sprintf("邀请用户赠送 %s", common.LogQuota(config.QuotaForInviter)))
|
||||
RecordLog(inviterId, LogTypeSystem, fmt.Sprintf("Invite users to give %s", common.LogQuota(config.QuotaForInviter)))
|
||||
}
|
||||
}
|
||||
// create default token
|
||||
@ -181,7 +181,7 @@ func (user *User) Update(updatePassword bool) error {
|
||||
|
||||
func (user *User) Delete() error {
|
||||
if user.Id == 0 {
|
||||
return errors.New("id 为空!")
|
||||
return errors.New("id is empty!")
|
||||
}
|
||||
blacklist.BanUser(user.Id)
|
||||
user.Username = fmt.Sprintf("deleted_%s", random.GetUUID())
|
||||
@ -197,7 +197,7 @@ func (user *User) ValidateAndFill() (err error) {
|
||||
// it won’t be used to build query conditions
|
||||
password := user.Password
|
||||
if user.Username == "" || password == "" {
|
||||
return errors.New("用户名或密码为空")
|
||||
return errors.New("Username or password is empty")
|
||||
}
|
||||
err = DB.Where("username = ?", user.Username).First(user).Error
|
||||
if err != nil {
|
||||
@ -205,19 +205,19 @@ func (user *User) ValidateAndFill() (err error) {
|
||||
// consider this case: a malicious user set his username as other's email
|
||||
err := DB.Where("email = ?", user.Username).First(user).Error
|
||||
if err != nil {
|
||||
return errors.New("用户名或密码错误,或用户已被封禁")
|
||||
return errors.New("Username or password is wrong, or user has been banned")
|
||||
}
|
||||
}
|
||||
okay := common.ValidatePasswordAndHash(password, user.Password)
|
||||
if !okay || user.Status != UserStatusEnabled {
|
||||
return errors.New("用户名或密码错误,或用户已被封禁")
|
||||
return errors.New("Username or password is wrong, or user has been banned")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (user *User) FillUserById() error {
|
||||
if user.Id == 0 {
|
||||
return errors.New("id 为空!")
|
||||
return errors.New("id is empty!")
|
||||
}
|
||||
DB.Where(User{Id: user.Id}).First(user)
|
||||
return nil
|
||||
@ -225,7 +225,7 @@ func (user *User) FillUserById() error {
|
||||
|
||||
func (user *User) FillUserByEmail() error {
|
||||
if user.Email == "" {
|
||||
return errors.New("email 为空!")
|
||||
return errors.New("email is empty!")
|
||||
}
|
||||
DB.Where(User{Email: user.Email}).First(user)
|
||||
return nil
|
||||
@ -233,7 +233,7 @@ func (user *User) FillUserByEmail() error {
|
||||
|
||||
func (user *User) FillUserByGitHubId() error {
|
||||
if user.GitHubId == "" {
|
||||
return errors.New("GitHub id 为空!")
|
||||
return errors.New("GitHub id is empty!")
|
||||
}
|
||||
DB.Where(User{GitHubId: user.GitHubId}).First(user)
|
||||
return nil
|
||||
@ -241,7 +241,7 @@ func (user *User) FillUserByGitHubId() error {
|
||||
|
||||
func (user *User) FillUserByLarkId() error {
|
||||
if user.LarkId == "" {
|
||||
return errors.New("lark id 为空!")
|
||||
return errors.New("lark id is empty!")
|
||||
}
|
||||
DB.Where(User{LarkId: user.LarkId}).First(user)
|
||||
return nil
|
||||
@ -249,7 +249,7 @@ func (user *User) FillUserByLarkId() error {
|
||||
|
||||
func (user *User) FillUserByOidcId() error {
|
||||
if user.OidcId == "" {
|
||||
return errors.New("oidc id 为空!")
|
||||
return errors.New("oidc id is empty!")
|
||||
}
|
||||
DB.Where(User{OidcId: user.OidcId}).First(user)
|
||||
return nil
|
||||
@ -257,7 +257,7 @@ func (user *User) FillUserByOidcId() error {
|
||||
|
||||
func (user *User) FillUserByWeChatId() error {
|
||||
if user.WeChatId == "" {
|
||||
return errors.New("WeChat id 为空!")
|
||||
return errors.New("WeChat id is empty!")
|
||||
}
|
||||
DB.Where(User{WeChatId: user.WeChatId}).First(user)
|
||||
return nil
|
||||
@ -265,7 +265,7 @@ func (user *User) FillUserByWeChatId() error {
|
||||
|
||||
func (user *User) FillUserByUsername() error {
|
||||
if user.Username == "" {
|
||||
return errors.New("username 为空!")
|
||||
return errors.New("username is empty!")
|
||||
}
|
||||
DB.Where(User{Username: user.Username}).First(user)
|
||||
return nil
|
||||
@ -297,7 +297,7 @@ func IsUsernameAlreadyTaken(username string) bool {
|
||||
|
||||
func ResetUserPasswordByEmail(email string, password string) error {
|
||||
if email == "" || password == "" {
|
||||
return errors.New("邮箱地址或密码为空!")
|
||||
return errors.New("Email address or password is empty!")
|
||||
}
|
||||
hashedPassword, err := common.Password2Hash(password)
|
||||
if err != nil {
|
||||
@ -371,7 +371,7 @@ func GetUserGroup(id int) (group string, err error) {
|
||||
|
||||
func IncreaseUserQuota(id int, quota int64) (err error) {
|
||||
if quota < 0 {
|
||||
return errors.New("quota 不能为负数!")
|
||||
return errors.New("quota cannot be negative!")
|
||||
}
|
||||
if config.BatchUpdateEnabled {
|
||||
addNewRecord(BatchUpdateTypeUserQuota, id, quota)
|
||||
@ -387,7 +387,7 @@ func increaseUserQuota(id int, quota int64) (err error) {
|
||||
|
||||
func DecreaseUserQuota(id int, quota int64) (err error) {
|
||||
if quota < 0 {
|
||||
return errors.New("quota 不能为负数!")
|
||||
return errors.New("quota cannot be negative!")
|
||||
}
|
||||
if config.BatchUpdateEnabled {
|
||||
addNewRecord(BatchUpdateTypeUserQuota, id, -quota)
|
||||
|
@ -30,16 +30,16 @@ func notifyRootUser(subject string, content string) {
|
||||
func DisableChannel(channelId int, channelName string, reason string) {
|
||||
model.UpdateChannelStatusById(channelId, model.ChannelStatusAutoDisabled)
|
||||
logger.SysLog(fmt.Sprintf("channel #%d has been disabled: %s", channelId, reason))
|
||||
subject := fmt.Sprintf("渠道「%s」(#%d)已被禁用", channelName, channelId)
|
||||
content := fmt.Sprintf("渠道「%s」(#%d)已被禁用,原因:%s", channelName, channelId, reason)
|
||||
subject := fmt.Sprintf("Channel %s (#%d) has been disabled", channelName, channelId)
|
||||
content := fmt.Sprintf("Channel %s (#%d) has been disabled, reason: %s", channelName, channelId, reason)
|
||||
notifyRootUser(subject, content)
|
||||
}
|
||||
|
||||
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("渠道 #%d 已被禁用", channelId)
|
||||
content := fmt.Sprintf("该渠道(#%d)在最近 %d 次调用中成功率为 %.2f%%,低于阈值 %.2f%%,因此被系统自动禁用。",
|
||||
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)
|
||||
notifyRootUser(subject, content)
|
||||
}
|
||||
@ -48,7 +48,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("渠道「%s」(#%d)已被启用", channelName, channelId)
|
||||
content := fmt.Sprintf("渠道「%s」(#%d)已被启用", channelName, channelId)
|
||||
subject := fmt.Sprintf("Channel「%s」(#%d)已被Enable", channelName, channelId)
|
||||
content := fmt.Sprintf("Channel「%s」(#%d)已被Enable", channelName, channelId)
|
||||
notifyRootUser(subject, content)
|
||||
}
|
||||
|
@ -2,23 +2,23 @@ package cohere
|
||||
|
||||
type Request struct {
|
||||
Message string `json:"message" required:"true"`
|
||||
Model string `json:"model,omitempty"` // 默认值为"command-r"
|
||||
Stream bool `json:"stream,omitempty"` // 默认值为false
|
||||
Model string `json:"model,omitempty"` // Default值为"command-r"
|
||||
Stream bool `json:"stream,omitempty"` // Default值为false
|
||||
Preamble string `json:"preamble,omitempty"`
|
||||
ChatHistory []ChatMessage `json:"chat_history,omitempty"`
|
||||
ConversationID string `json:"conversation_id,omitempty"`
|
||||
PromptTruncation string `json:"prompt_truncation,omitempty"` // 默认值为"AUTO"
|
||||
PromptTruncation string `json:"prompt_truncation,omitempty"` // Default值为"AUTO"
|
||||
Connectors []Connector `json:"connectors,omitempty"`
|
||||
Documents []Document `json:"documents,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"` // 默认值为0.3
|
||||
Temperature *float64 `json:"temperature,omitempty"` // Default值为0.3
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
MaxInputTokens int `json:"max_input_tokens,omitempty"`
|
||||
K int `json:"k,omitempty"` // 默认值为0
|
||||
P *float64 `json:"p,omitempty"` // 默认值为0.75
|
||||
K int `json:"k,omitempty"` // Default值为0
|
||||
P *float64 `json:"p,omitempty"` // Default值为0.75
|
||||
Seed int `json:"seed,omitempty"`
|
||||
StopSequences []string `json:"stop_sequences,omitempty"`
|
||||
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // 默认值为0.0
|
||||
PresencePenalty *float64 `json:"presence_penalty,omitempty"` // 默认值为0.0
|
||||
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // Default值为0.0
|
||||
PresencePenalty *float64 `json:"presence_penalty,omitempty"` // Default值为0.0
|
||||
Tools []Tool `json:"tools,omitempty"`
|
||||
ToolResults []ToolResult `json:"tool_results,omitempty"`
|
||||
}
|
||||
|
@ -6,39 +6,39 @@ type Message struct {
|
||||
}
|
||||
|
||||
type ChatRequest struct {
|
||||
// 模型名称,可选值包括 hunyuan-lite、hunyuan-standard、hunyuan-standard-256K、hunyuan-pro。
|
||||
// 各模型介绍请阅读 [产品概述](https://cloud.tencent.com/document/product/1729/104753) 中的说明。
|
||||
// Model name,Optional values包括 hunyuan-lite、hunyuan-standard、hunyuan-standard-256K、hunyuan-pro。
|
||||
// 各Model介绍请阅读 [产品概述](https://cloud.tencent.com/document/product/1729/104753) 中的说明。
|
||||
//
|
||||
// 注意:
|
||||
// 不同的模型计费不同,请根据 [购买指南](https://cloud.tencent.com/document/product/1729/97731) 按需调用。
|
||||
// Note:
|
||||
// 不同的Model计费不同,请根据 [购买指南](https://cloud.tencent.com/document/product/1729/97731) 按需调用。
|
||||
Model *string `json:"Model"`
|
||||
// 聊天上下文信息。
|
||||
// Chat上下文信息。
|
||||
// 说明:
|
||||
// 1. 长度最多为 40,按对话时间从旧到新在数组中排列。
|
||||
// 2. Message.Role 可选值:system、user、assistant。
|
||||
// 1. 长度最多为 40,按对话Time从旧到新在数Group中排列。
|
||||
// 2. Message.Role Optional values:system、user、assistant。
|
||||
// 其中,system 角色可选,如存在则必须位于列表的最开始。user 和 assistant 需交替出现(一问一答),以 user 提问开始和结束,且 Content 不能为空。Role 的顺序示例:[system(可选) user assistant user assistant user ...]。
|
||||
// 3. Messages 中 Content 总长度不能超过模型输入长度上限(可参考 [产品概述](https://cloud.tencent.com/document/product/1729/104753) 文档),超过则会截断最前面的内容,只保留尾部内容。
|
||||
// 3. Messages 中 Content 总长度不能超过ModelEnter长度上限(可参考 [产品概述](https://cloud.tencent.com/document/product/1729/104753) 文档),超过则会截断最前面的内容,只保留尾部内容。
|
||||
Messages []*Message `json:"Messages"`
|
||||
// 流式调用开关。
|
||||
// 说明:
|
||||
// 1. 未传值时默认为非流式调用(false)。
|
||||
// 1. 未传值时Default为非流式调用(false)。
|
||||
// 2. 流式调用时以 SSE 协议增量返回结果(返回值取 Choices[n].Delta 中的值,需要拼接增量数据才能获得完整结果)。
|
||||
// 3. 非流式调用时:
|
||||
// 调用方式与普通 HTTP 请求无异。
|
||||
// 接口响应耗时较长,**如需更低时延建议设置为 true**。
|
||||
// 调用方式与普通 HTTP 请求None异。
|
||||
// 接口响应耗时较长,**如需更低时延建议Settings为 true**。
|
||||
// 只返回一次最终结果(返回值取 Choices[n].Message 中的值)。
|
||||
//
|
||||
// 注意:
|
||||
// Note:
|
||||
// 通过 SDK 调用时,流式和非流式调用需用**不同的方式**获取返回值,具体参考 SDK 中的注释或示例(在各语言 SDK 代码仓库的 examples/hunyuan/v20230901/ 目录中)。
|
||||
Stream *bool `json:"Stream"`
|
||||
// 说明:
|
||||
// 1. 影响输出文本的多样性,取值越大,生成文本的多样性越强。
|
||||
// 2. 取值区间为 [0.0, 1.0],未传值时使用各模型推荐值。
|
||||
// 2. 取值区间为 [0.0, 1.0],未传值时使用各Model推荐值。
|
||||
// 3. 非必要不建议使用,不合理的取值会影响效果。
|
||||
TopP *float64 `json:"TopP"`
|
||||
// 说明:
|
||||
// 1. 较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定。
|
||||
// 2. 取值区间为 [0.0, 2.0],未传值时使用各模型推荐值。
|
||||
// 2. 取值区间为 [0.0, 2.0],未传值时使用各Model推荐值。
|
||||
// 3. 非必要不建议使用,不合理的取值会影响效果。
|
||||
Temperature *float64 `json:"Temperature"`
|
||||
}
|
||||
@ -62,10 +62,10 @@ type ResponseChoices struct {
|
||||
|
||||
type ChatResponse struct {
|
||||
Choices []ResponseChoices `json:"Choices,omitempty"` // 结果
|
||||
Created int64 `json:"Created,omitempty"` // unix 时间戳的字符串
|
||||
Created int64 `json:"Created,omitempty"` // unix Time戳的字符串
|
||||
Id string `json:"Id,omitempty"` // 会话 id
|
||||
Usage Usage `json:"Usage,omitempty"` // token 数量
|
||||
Error Error `json:"Error,omitempty"` // 错误信息 注意:此字段可能返回 null,表示取不到有效值
|
||||
Error Error `json:"Error,omitempty"` // 错误信息 Note:此字段可能返回 null,表示取不到有效值
|
||||
Note string `json:"Note,omitempty"` // 注释
|
||||
ReqID string `json:"Req_id,omitempty"` // 唯一请求 Id,每次请求都会返回。用于反馈接口入参
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ func PostConsumeQuota(ctx context.Context, tokenId int, quotaDelta int64, totalQ
|
||||
}
|
||||
// totalQuota is total quota consumed
|
||||
if totalQuota != 0 {
|
||||
logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio)
|
||||
logContent := fmt.Sprintf("model rate %.2f, group rate %.2f", modelRatio, groupRatio)
|
||||
model.RecordConsumeLog(ctx, userId, channelId, int(totalQuota), 0, modelName, tokenName, totalQuota, logContent)
|
||||
model.UpdateUserUsedQuotaAndRequestCount(userId, totalQuota)
|
||||
model.UpdateChannelUsedQuota(channelId, totalQuota)
|
||||
|
@ -125,9 +125,9 @@ func postConsumeQuota(ctx context.Context, usage *relaymodel.Usage, meta *meta.M
|
||||
}
|
||||
var extraLog string
|
||||
if systemPromptReset {
|
||||
extraLog = " (注意系统提示词已被重置)"
|
||||
extraLog = " (NoteSystemPrompt词已被重置)"
|
||||
}
|
||||
logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f,补全倍率 %.2f%s", modelRatio, groupRatio, completionRatio, extraLog)
|
||||
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)
|
||||
model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota)
|
||||
model.UpdateChannelUsedQuota(meta.ChannelId, quota)
|
||||
|
@ -225,7 +225,7 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus
|
||||
}
|
||||
if quota >= 0 {
|
||||
tokenName := c.GetString(ctxkey.TokenName)
|
||||
logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio)
|
||||
logContent := fmt.Sprintf("model rate %.2f, group rate %.2f", modelRatio, groupRatio)
|
||||
model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, 0, 0, imageRequest.Model, tokenName, quota, logContent)
|
||||
model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota)
|
||||
channelId := c.GetInt(ctxkey.ChannelId)
|
||||
|
@ -7,7 +7,7 @@
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta
|
||||
name="description"
|
||||
content="OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用"
|
||||
content="OpenAI interface aggregation management, supports multiple channels including Azure, can be used for secondary distribution management key, only single executable file, Docker image has been packaged, one-click deployment, out of the box"
|
||||
/>
|
||||
<title>One API</title>
|
||||
</head>
|
||||
|
@ -62,11 +62,11 @@ function App() {
|
||||
process.env.REACT_APP_VERSION !== ''
|
||||
) {
|
||||
showNotice(
|
||||
`新版本可用:${data.version},请使用快捷键 Shift + F5 刷新页面`
|
||||
`New version available: ${data.version}, please refresh the page using the shortcut key Shift + F5`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
showError('无法正常连接至服务器!');
|
||||
showError('Unable to connect to the server normally!');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -31,7 +31,7 @@ function renderType(type) {
|
||||
for (let i = 0; i < CHANNEL_OPTIONS.length; i++) {
|
||||
type2label[CHANNEL_OPTIONS[i].value] = CHANNEL_OPTIONS[i];
|
||||
}
|
||||
type2label[0] = { value: 0, text: '未知类型', color: 'grey' };
|
||||
type2label[0] = { value: 0, text: 'Unknown type', color: 'grey' };
|
||||
}
|
||||
return <Label basic color={type2label[type]?.color}>{type2label[type] ? type2label[type].text : type}</Label>;
|
||||
}
|
||||
@ -42,7 +42,7 @@ function renderBalance(type, balance) {
|
||||
return <span>${balance.toFixed(2)}</span>;
|
||||
case 4: // CloseAI
|
||||
return <span>¥{balance.toFixed(2)}</span>;
|
||||
case 8: // 自定义
|
||||
case 8: // Custom
|
||||
return <span>${balance.toFixed(2)}</span>;
|
||||
case 5: // OpenAI-SB
|
||||
return <span>¥{(balance / 10000).toFixed(2)}</span>;
|
||||
@ -57,7 +57,7 @@ function renderBalance(type, balance) {
|
||||
case 44: // SiliconFlow
|
||||
return <span>¥{balance.toFixed(2)}</span>;
|
||||
default:
|
||||
return <span>不支持</span>;
|
||||
return <span>Not supported</span>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,7 +178,7 @@ const ChannelsTable = () => {
|
||||
}
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('操作成功完成!');
|
||||
showSuccess('Operation successfully completed!');
|
||||
let channel = res.data.data;
|
||||
let newChannels = [...channels];
|
||||
let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
|
||||
@ -196,14 +196,14 @@ const ChannelsTable = () => {
|
||||
const renderStatus = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return <Label basic color='green'>已启用</Label>;
|
||||
return <Label basic color='green'>Enabled</Label>;
|
||||
case 2:
|
||||
return (
|
||||
<Popup
|
||||
trigger={<Label basic color='red'>
|
||||
已禁用
|
||||
Disabled
|
||||
</Label>}
|
||||
content='本渠道被手动禁用'
|
||||
content='本Channel被手动Disable'
|
||||
basic
|
||||
/>
|
||||
);
|
||||
@ -211,16 +211,16 @@ const ChannelsTable = () => {
|
||||
return (
|
||||
<Popup
|
||||
trigger={<Label basic color='yellow'>
|
||||
已禁用
|
||||
Disabled
|
||||
</Label>}
|
||||
content='本渠道被程序自动禁用'
|
||||
content='本Channel被程序自动Disable'
|
||||
basic
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Label basic color='grey'>
|
||||
未知状态
|
||||
Unknown status
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
@ -228,9 +228,9 @@ const ChannelsTable = () => {
|
||||
|
||||
const renderResponseTime = (responseTime) => {
|
||||
let time = responseTime / 1000;
|
||||
time = time.toFixed(2) + ' 秒';
|
||||
time = time.toFixed(2) + 's';
|
||||
if (responseTime === 0) {
|
||||
return <Label basic color='grey'>未测试</Label>;
|
||||
return <Label basic color='grey'>Not tested</Label>;
|
||||
} else if (responseTime <= 1000) {
|
||||
return <Label basic color='green'>{time}</Label>;
|
||||
} else if (responseTime <= 3000) {
|
||||
@ -277,7 +277,7 @@ const ChannelsTable = () => {
|
||||
newChannels[realIdx].response_time = time * 1000;
|
||||
newChannels[realIdx].test_time = Date.now() / 1000;
|
||||
setChannels(newChannels);
|
||||
showInfo(`渠道 ${name} 测试成功,模型 ${model},耗时 ${time.toFixed(2)} 秒。`);
|
||||
showInfo(`Channel ${name} Test成功,Model ${model},耗时 ${time.toFixed(2)}s。`);
|
||||
} 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('已成功开始测试渠道,请刷新页面查看结果。');
|
||||
showInfo('已成功开始TestChannel,请Refresh页面查看结果。');
|
||||
} 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(`已删除所有禁用渠道,共计 ${data} 个`);
|
||||
showSuccess(`已Delete所有DisableChannel,共计 ${data} 个`);
|
||||
await refresh();
|
||||
} else {
|
||||
showError(message);
|
||||
@ -318,7 +318,7 @@ const ChannelsTable = () => {
|
||||
newChannels[realIdx].balance = balance;
|
||||
newChannels[realIdx].balance_updated_time = Date.now() / 1000;
|
||||
setChannels(newChannels);
|
||||
showInfo(`渠道 ${name} 余额更新成功!`);
|
||||
showInfo(`Channel ${name} balance updated successfully!`);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -329,7 +329,7 @@ const ChannelsTable = () => {
|
||||
const res = await API.get(`/api/channel/update_balance`);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showInfo('已更新完毕所有已启用渠道余额!');
|
||||
showInfo('The balance of all enabled channels has been updated!');
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -368,7 +368,7 @@ const ChannelsTable = () => {
|
||||
icon='search'
|
||||
fluid
|
||||
iconPosition='left'
|
||||
placeholder='搜索渠道的 ID,名称和密钥 ...'
|
||||
placeholder='Search for channel ID, name and key ...'
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={handleKeywordChange}
|
||||
@ -380,11 +380,11 @@ const ChannelsTable = () => {
|
||||
setShowPrompt(false);
|
||||
setPromptShown(promptID);
|
||||
}}>
|
||||
OpenAI 渠道已经不再支持通过 key 获取余额,因此余额显示为 0。对于支持的渠道类型,请点击余额进行刷新。
|
||||
OpenAI Channel已经不再支持通过 key 获取Balance,因此Balance显示为 0。对于支持的ChannelType,请点击Balance进行Refresh。
|
||||
<br/>
|
||||
渠道测试仅支持 chat 模型,优先使用 gpt-3.5-turbo,如果该模型不可用则使用你所配置的模型列表中的第一个模型。
|
||||
ChannelTest仅支持 chat Model,优先使用 gpt-3.5-turbo,如果该Model不可用则使用你所配置的Model列表中的第一个Model。
|
||||
<br/>
|
||||
点击下方详情按钮可以显示余额以及设置额外的测试模型。
|
||||
点击下方Details按钮可以显示Balance以及Settings额外的TestModel。
|
||||
</Message>
|
||||
)
|
||||
}
|
||||
@ -405,7 +405,7 @@ const ChannelsTable = () => {
|
||||
sortChannel('name');
|
||||
}}
|
||||
>
|
||||
名称
|
||||
Name
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -413,7 +413,7 @@ const ChannelsTable = () => {
|
||||
sortChannel('group');
|
||||
}}
|
||||
>
|
||||
分组
|
||||
Group
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -421,7 +421,7 @@ const ChannelsTable = () => {
|
||||
sortChannel('type');
|
||||
}}
|
||||
>
|
||||
类型
|
||||
Type
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -429,7 +429,7 @@ const ChannelsTable = () => {
|
||||
sortChannel('status');
|
||||
}}
|
||||
>
|
||||
状态
|
||||
Status
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -437,7 +437,7 @@ const ChannelsTable = () => {
|
||||
sortChannel('response_time');
|
||||
}}
|
||||
>
|
||||
响应时间
|
||||
Response time
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -446,7 +446,7 @@ const ChannelsTable = () => {
|
||||
}}
|
||||
hidden={!showDetail}
|
||||
>
|
||||
余额
|
||||
Balance
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -456,8 +456,8 @@ const ChannelsTable = () => {
|
||||
>
|
||||
优先级
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell hidden={!showDetail}>测试模型</Table.HeaderCell>
|
||||
<Table.HeaderCell>操作</Table.HeaderCell>
|
||||
<Table.HeaderCell hidden={!showDetail}>TestModel</Table.HeaderCell>
|
||||
<Table.HeaderCell>Operation</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
|
||||
@ -472,13 +472,13 @@ const ChannelsTable = () => {
|
||||
return (
|
||||
<Table.Row key={channel.id}>
|
||||
<Table.Cell>{channel.id}</Table.Cell>
|
||||
<Table.Cell>{channel.name ? channel.name : '无'}</Table.Cell>
|
||||
<Table.Cell>{channel.name ? channel.name : 'None'}</Table.Cell>
|
||||
<Table.Cell>{renderGroup(channel.group)}</Table.Cell>
|
||||
<Table.Cell>{renderType(channel.type)}</Table.Cell>
|
||||
<Table.Cell>{renderStatus(channel.status)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Popup
|
||||
content={channel.test_time ? renderTimestamp(channel.test_time) : '未测试'}
|
||||
content={channel.test_time ? renderTimestamp(channel.test_time) : 'Not tested'}
|
||||
key={channel.id}
|
||||
trigger={renderResponseTime(channel.response_time)}
|
||||
basic
|
||||
@ -507,13 +507,13 @@ const ChannelsTable = () => {
|
||||
}}>
|
||||
<input style={{ maxWidth: '60px' }} />
|
||||
</Input>}
|
||||
content='渠道选择优先级,越高越优先'
|
||||
content='Channel选择优先级,越高越优先'
|
||||
basic
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell hidden={!showDetail}>
|
||||
<Dropdown
|
||||
placeholder='请选择测试模型'
|
||||
placeholder='请选择TestModel'
|
||||
selection
|
||||
options={channel.model_options}
|
||||
defaultValue={channel.test_model}
|
||||
@ -531,7 +531,7 @@ const ChannelsTable = () => {
|
||||
testChannel(channel.id, channel.name, idx, channel.test_model);
|
||||
}}
|
||||
>
|
||||
测试
|
||||
Test
|
||||
</Button>
|
||||
{/*<Button*/}
|
||||
{/* size={'small'}*/}
|
||||
@ -541,12 +541,12 @@ const ChannelsTable = () => {
|
||||
{/* updateChannelBalance(channel.id, channel.name, idx);*/}
|
||||
{/* }}*/}
|
||||
{/*>*/}
|
||||
{/* 更新余额*/}
|
||||
{/* Update balance*/}
|
||||
{/*</Button>*/}
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative>
|
||||
删除
|
||||
Delete
|
||||
</Button>
|
||||
}
|
||||
on='click'
|
||||
@ -559,7 +559,7 @@ const ChannelsTable = () => {
|
||||
manageChannel(channel.id, 'delete', idx);
|
||||
}}
|
||||
>
|
||||
删除渠道 {channel.name}
|
||||
Delete channel {channel.name}
|
||||
</Button>
|
||||
</Popup>
|
||||
<Button
|
||||
@ -572,14 +572,14 @@ const ChannelsTable = () => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{channel.status === 1 ? '禁用' : '启用'}
|
||||
{channel.status === 1 ? 'Disable' : 'Enable'}
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
as={Link}
|
||||
to={'/channel/edit/' + channel.id}
|
||||
>
|
||||
编辑
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
@ -592,20 +592,20 @@ const ChannelsTable = () => {
|
||||
<Table.Row>
|
||||
<Table.HeaderCell colSpan={showDetail ? "10" : "8"}>
|
||||
<Button size='small' as={Link} to='/channel/add' loading={loading}>
|
||||
添加新的渠道
|
||||
Add a new channel
|
||||
</Button>
|
||||
<Button size='small' loading={loading} onClick={()=>{testChannels("all")}}>
|
||||
测试所有渠道
|
||||
Test all channels
|
||||
</Button>
|
||||
<Button size='small' loading={loading} onClick={()=>{testChannels("disabled")}}>
|
||||
测试禁用渠道
|
||||
TestDisableChannel
|
||||
</Button>
|
||||
{/*<Button size='small' onClick={updateAllChannelsBalance}*/}
|
||||
{/* loading={loading || updatingBalance}>更新已启用渠道余额</Button>*/}
|
||||
{/* loading={loading || updatingBalance}>Update the balance of enabled channels</Button>*/}
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' loading={loading}>
|
||||
删除禁用渠道
|
||||
DeleteDisableChannel
|
||||
</Button>
|
||||
}
|
||||
on='click'
|
||||
@ -613,7 +613,7 @@ const ChannelsTable = () => {
|
||||
hoverable
|
||||
>
|
||||
<Button size='small' loading={loading} negative onClick={deleteAllDisabledChannels}>
|
||||
确认删除
|
||||
Confirm deletion
|
||||
</Button>
|
||||
</Popup>
|
||||
<Pagination
|
||||
@ -627,8 +627,8 @@ const ChannelsTable = () => {
|
||||
(channels.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
|
||||
}
|
||||
/>
|
||||
<Button size='small' onClick={refresh} loading={loading}>刷新</Button>
|
||||
<Button size='small' onClick={toggleShowDetail}>{showDetail ? "隐藏详情" : "详情"}</Button>
|
||||
<Button size='small' onClick={refresh} loading={loading}>Refresh</Button>
|
||||
<Button size='small' onClick={toggleShowDetail}>{showDetail ? "隐藏Details" : "Details"}</Button>
|
||||
</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Footer>
|
||||
|
@ -8,7 +8,7 @@ const GitHubOAuth = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [userState, userDispatch] = useContext(UserContext);
|
||||
const [prompt, setPrompt] = useState('处理中...');
|
||||
const [prompt, setPrompt] = useState('Processing...');
|
||||
const [processing, setProcessing] = useState(true);
|
||||
|
||||
let navigate = useNavigate();
|
||||
@ -18,23 +18,23 @@ const GitHubOAuth = () => {
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (message === 'bind') {
|
||||
showSuccess('绑定成功!');
|
||||
showSuccess('Binding successful!');
|
||||
navigate('/setting');
|
||||
} else {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('Login successful!');
|
||||
navigate('/');
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
if (count === 0) {
|
||||
setPrompt(`操作失败,重定向至登录界面中...`);
|
||||
setPrompt(`Operation failed, redirecting to login screen...`);
|
||||
navigate('/setting'); // in case this is failed to bind GitHub
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
setPrompt(`出现错误,第 ${count} 次重试中...`);
|
||||
setPrompt(`An error occurred, retrying ${count}...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, count * 2000));
|
||||
await sendCode(code, state, count);
|
||||
}
|
||||
|
@ -9,50 +9,50 @@ import '../index.css';
|
||||
// Header Buttons
|
||||
let headerButtons = [
|
||||
{
|
||||
name: '首页',
|
||||
name: 'Home',
|
||||
to: '/',
|
||||
icon: 'home'
|
||||
},
|
||||
{
|
||||
name: '渠道',
|
||||
name: 'Channel',
|
||||
to: '/channel',
|
||||
icon: 'sitemap',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '令牌',
|
||||
name: 'API Keys',
|
||||
to: '/token',
|
||||
icon: 'key'
|
||||
},
|
||||
{
|
||||
name: '兑换',
|
||||
name: 'Redeem',
|
||||
to: '/redemption',
|
||||
icon: 'dollar sign',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '充值',
|
||||
name: 'Recharge',
|
||||
to: '/topup',
|
||||
icon: 'cart'
|
||||
},
|
||||
{
|
||||
name: '用户',
|
||||
name: 'Users',
|
||||
to: '/user',
|
||||
icon: 'user',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '日志',
|
||||
name: 'Logs',
|
||||
to: '/log',
|
||||
icon: 'book'
|
||||
},
|
||||
{
|
||||
name: '设置',
|
||||
name: 'Settings',
|
||||
to: '/setting',
|
||||
icon: 'setting'
|
||||
},
|
||||
{
|
||||
name: '关于',
|
||||
name: 'About',
|
||||
to: '/about',
|
||||
icon: 'info circle'
|
||||
}
|
||||
@ -60,7 +60,7 @@ let headerButtons = [
|
||||
|
||||
if (localStorage.getItem('chat_link')) {
|
||||
headerButtons.splice(1, 0, {
|
||||
name: '聊天',
|
||||
name: 'Chat',
|
||||
to: '/chat',
|
||||
icon: 'comments'
|
||||
});
|
||||
@ -77,7 +77,7 @@ const Header = () => {
|
||||
async function logout() {
|
||||
setShowSidebar(false);
|
||||
await API.get('/api/user/logout');
|
||||
showSuccess('注销成功!');
|
||||
showSuccess('Logout successful!');
|
||||
userDispatch({ type: 'logout' });
|
||||
localStorage.removeItem('user');
|
||||
navigate('/login');
|
||||
@ -152,7 +152,7 @@ const Header = () => {
|
||||
{renderButtons(true)}
|
||||
<Menu.Item>
|
||||
{userState.user ? (
|
||||
<Button onClick={logout}>注销</Button>
|
||||
<Button onClick={logout}>Log out</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
@ -161,7 +161,7 @@ const Header = () => {
|
||||
navigate('/login');
|
||||
}}
|
||||
>
|
||||
登录
|
||||
Log in
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@ -169,7 +169,7 @@ const Header = () => {
|
||||
navigate('/register');
|
||||
}}
|
||||
>
|
||||
注册
|
||||
Sign up
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@ -202,12 +202,12 @@ const Header = () => {
|
||||
className='link item'
|
||||
>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item onClick={logout}>注销</Dropdown.Item>
|
||||
<Dropdown.Item onClick={logout}>Log out</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
) : (
|
||||
<Menu.Item
|
||||
name='登录'
|
||||
name='Log in'
|
||||
as={Link}
|
||||
to='/login'
|
||||
className='btn btn-link'
|
||||
|
@ -8,7 +8,7 @@ const LarkOAuth = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [userState, userDispatch] = useContext(UserContext);
|
||||
const [prompt, setPrompt] = useState('处理中...');
|
||||
const [prompt, setPrompt] = useState('Processing...');
|
||||
const [processing, setProcessing] = useState(true);
|
||||
|
||||
let navigate = useNavigate();
|
||||
@ -18,23 +18,23 @@ const LarkOAuth = () => {
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (message === 'bind') {
|
||||
showSuccess('绑定成功!');
|
||||
showSuccess('Binding successful!');
|
||||
navigate('/setting');
|
||||
} else {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('Login successful!');
|
||||
navigate('/');
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
if (count === 0) {
|
||||
setPrompt(`操作失败,重定向至登录界面中...`);
|
||||
setPrompt(`Operation failed, redirecting to login screen...`);
|
||||
navigate('/setting'); // in case this is failed to bind lark
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
setPrompt(`出现错误,第 ${count} 次重试中...`);
|
||||
setPrompt(`An error occurred, retrying ${count}...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, count * 2000));
|
||||
await sendCode(code, state, count);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ const Loading = ({ prompt: name = 'page' }) => {
|
||||
return (
|
||||
<Segment style={{ height: 100 }}>
|
||||
<Dimmer active inverted>
|
||||
<Loader indeterminate>加载{name}中...</Loader>
|
||||
<Loader indeterminate>Loading {name}...</Loader>
|
||||
</Dimmer>
|
||||
</Segment>
|
||||
);
|
||||
|
@ -22,7 +22,7 @@ const LoginForm = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get('expired')) {
|
||||
showError('未登录或登录已过期,请重新登录!');
|
||||
showError('Not logged in or login has expired, please log in again!');
|
||||
}
|
||||
let status = localStorage.getItem('status');
|
||||
if (status) {
|
||||
@ -46,7 +46,7 @@ const LoginForm = () => {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
navigate('/');
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('Login successful!');
|
||||
setShowWeChatLoginModal(false);
|
||||
} else {
|
||||
showError(message);
|
||||
@ -71,11 +71,11 @@ const LoginForm = () => {
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
if (username === 'root' && password === '123456') {
|
||||
navigate('/user/edit');
|
||||
showSuccess('登录成功!');
|
||||
showWarning('请立刻修改默认密码!');
|
||||
showSuccess('Login successful!');
|
||||
showWarning('Please change the default password immediately!');
|
||||
} else {
|
||||
navigate('/token');
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('Login successful!');
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
@ -87,7 +87,7 @@ const LoginForm = () => {
|
||||
<Grid textAlign='center' style={{ marginTop: '48px' }}>
|
||||
<Grid.Column style={{ maxWidth: 450 }}>
|
||||
<Header as='h2' color='' textAlign='center'>
|
||||
<Image src={logo} /> 用户登录
|
||||
<Image src={logo} /> User login
|
||||
</Header>
|
||||
<Form size='large'>
|
||||
<Segment>
|
||||
@ -95,7 +95,7 @@ const LoginForm = () => {
|
||||
fluid
|
||||
icon='user'
|
||||
iconPosition='left'
|
||||
placeholder='用户名 / 邮箱地址'
|
||||
placeholder='Username / Email address'
|
||||
name='username'
|
||||
value={username}
|
||||
onChange={handleChange}
|
||||
@ -104,25 +104,25 @@ const LoginForm = () => {
|
||||
fluid
|
||||
icon='lock'
|
||||
iconPosition='left'
|
||||
placeholder='密码'
|
||||
placeholder='Password'
|
||||
name='password'
|
||||
type='password'
|
||||
value={password}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button color='green' fluid size='large' onClick={handleSubmit}>
|
||||
登录
|
||||
Log in
|
||||
</Button>
|
||||
</Segment>
|
||||
</Form>
|
||||
<Message>
|
||||
忘记密码?
|
||||
Forget password?
|
||||
<Link to='/reset' className='btn btn-link'>
|
||||
点击重置
|
||||
Click to reset
|
||||
</Link>
|
||||
; 没有账户?
|
||||
; No account?
|
||||
<Link to='/register' className='btn btn-link'>
|
||||
点击注册
|
||||
Click to register
|
||||
</Link>
|
||||
</Message>
|
||||
{status.github_oauth || status.wechat_login || status.lark_client_id ? (
|
||||
@ -186,13 +186,13 @@ const LoginForm = () => {
|
||||
<Image src={status.wechat_qrcode} fluid />
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<p>
|
||||
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
|
||||
Scan the QR code with WeChat, follow the official account and enter 'verification code' to get the verification code (valid within three minutes)
|
||||
</p>
|
||||
</div>
|
||||
<Form size='large'>
|
||||
<Form.Input
|
||||
fluid
|
||||
placeholder='验证码'
|
||||
placeholder='Verification code'
|
||||
name='wechat_verification_code'
|
||||
value={inputs.wechat_verification_code}
|
||||
onChange={handleChange}
|
||||
@ -203,7 +203,7 @@ const LoginForm = () => {
|
||||
size='large'
|
||||
onClick={onSubmitWeChatVerificationCode}
|
||||
>
|
||||
登录
|
||||
Log in
|
||||
</Button>
|
||||
</Form>
|
||||
</Modal.Description>
|
||||
|
@ -14,30 +14,30 @@ function renderTimestamp(timestamp) {
|
||||
}
|
||||
|
||||
const MODE_OPTIONS = [
|
||||
{ key: 'all', text: '全部用户', value: 'all' },
|
||||
{ key: 'self', text: '当前用户', value: 'self' }
|
||||
{ key: 'all', text: 'All users', value: 'all' },
|
||||
{ key: 'self', text: 'Current user', value: 'self' }
|
||||
];
|
||||
|
||||
const LOG_OPTIONS = [
|
||||
{ key: '0', text: '全部', value: 0 },
|
||||
{ key: '1', text: '充值', value: 1 },
|
||||
{ key: '2', text: '消费', value: 2 },
|
||||
{ key: '3', text: '管理', value: 3 },
|
||||
{ key: '4', text: '系统', value: 4 }
|
||||
{ key: '0', text: 'All', value: 0 },
|
||||
{ key: '1', text: 'Recharge', value: 1 },
|
||||
{ key: '2', text: 'Consumption', value: 2 },
|
||||
{ key: '3', text: 'Management', value: 3 },
|
||||
{ key: '4', text: 'System', value: 4 }
|
||||
];
|
||||
|
||||
function renderType(type) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return <Label basic color='green'> 充值 </Label>;
|
||||
return <Label basic color='green'> Recharge </Label>;
|
||||
case 2:
|
||||
return <Label basic color='olive'> 消费 </Label>;
|
||||
return <Label basic color='olive'> Consumption </Label>;
|
||||
case 3:
|
||||
return <Label basic color='orange'> 管理 </Label>;
|
||||
return <Label basic color='orange'> Management </Label>;
|
||||
case 4:
|
||||
return <Label basic color='purple'> 系统 </Label>;
|
||||
return <Label basic color='purple'> System </Label>;
|
||||
default:
|
||||
return <Label basic color='black'> 未知 </Label>;
|
||||
return <Label basic color='black'> Unknown </Label>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,34 +199,34 @@ const LogsTable = () => {
|
||||
<>
|
||||
<Segment>
|
||||
<Header as='h3'>
|
||||
使用明细(总消耗额度:
|
||||
Usages(Total consumption limit:
|
||||
{showStat && renderQuota(stat.quota)}
|
||||
{!showStat && <span onClick={handleEyeClick} style={{ cursor: 'pointer', color: 'gray' }}>点击查看</span>}
|
||||
{!showStat && <span onClick={handleEyeClick} style={{ cursor: 'pointer', color: 'gray' }}>click to view</span>}
|
||||
)
|
||||
</Header>
|
||||
<Form>
|
||||
<Form.Group>
|
||||
<Form.Input fluid label={'令牌名称'} width={3} value={token_name}
|
||||
placeholder={'可选值'} name='token_name' onChange={handleInputChange} />
|
||||
<Form.Input fluid label='模型名称' width={3} value={model_name} placeholder='可选值'
|
||||
<Form.Input fluid label={'Key name'} width={3} value={token_name}
|
||||
placeholder={'Optional values'} name='token_name' onChange={handleInputChange} />
|
||||
<Form.Input fluid label='Model name' width={3} value={model_name} placeholder='Optional values'
|
||||
name='model_name'
|
||||
onChange={handleInputChange} />
|
||||
<Form.Input fluid label='起始时间' width={4} value={start_timestamp} type='datetime-local'
|
||||
<Form.Input fluid label='Start time' width={4} value={start_timestamp} type='datetime-local'
|
||||
name='start_timestamp'
|
||||
onChange={handleInputChange} />
|
||||
<Form.Input fluid label='结束时间' width={4} value={end_timestamp} type='datetime-local'
|
||||
<Form.Input fluid label='End time' width={4} value={end_timestamp} type='datetime-local'
|
||||
name='end_timestamp'
|
||||
onChange={handleInputChange} />
|
||||
<Form.Button fluid label='操作' width={2} onClick={refresh}>查询</Form.Button>
|
||||
<Form.Button fluid label='Operation' width={2} onClick={refresh}>Query</Form.Button>
|
||||
</Form.Group>
|
||||
{
|
||||
isAdminUser && <>
|
||||
<Form.Group>
|
||||
<Form.Input fluid label={'渠道 ID'} width={3} value={channel}
|
||||
placeholder='可选值' name='channel'
|
||||
<Form.Input fluid label={'Channel ID'} width={3} value={channel}
|
||||
placeholder='Optional values' name='channel'
|
||||
onChange={handleInputChange} />
|
||||
<Form.Input fluid label={'用户名称'} width={3} value={username}
|
||||
placeholder={'可选值'} name='username'
|
||||
<Form.Input fluid label={'User name'} width={3} value={username}
|
||||
placeholder={'Optional values'} name='username'
|
||||
onChange={handleInputChange} />
|
||||
|
||||
</Form.Group>
|
||||
@ -243,7 +243,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={3}
|
||||
>
|
||||
时间
|
||||
Time
|
||||
</Table.HeaderCell>
|
||||
{
|
||||
isAdminUser && <Table.HeaderCell
|
||||
@ -253,7 +253,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
渠道
|
||||
Channel
|
||||
</Table.HeaderCell>
|
||||
}
|
||||
{
|
||||
@ -264,7 +264,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
用户
|
||||
Users
|
||||
</Table.HeaderCell>
|
||||
}
|
||||
<Table.HeaderCell
|
||||
@ -274,7 +274,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
令牌
|
||||
API Keys
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -283,7 +283,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
类型
|
||||
Type
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -292,7 +292,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={2}
|
||||
>
|
||||
模型
|
||||
Model
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -301,7 +301,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
提示
|
||||
Prompt
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -310,7 +310,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
补全
|
||||
Completion
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -328,7 +328,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={isAdminUser ? 4 : 6}
|
||||
>
|
||||
详情
|
||||
Details
|
||||
</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
@ -370,7 +370,7 @@ const LogsTable = () => {
|
||||
<Table.Row>
|
||||
<Table.HeaderCell colSpan={'10'}>
|
||||
<Select
|
||||
placeholder='选择明细分类'
|
||||
placeholder='Select detail category'
|
||||
options={LOG_OPTIONS}
|
||||
style={{ marginRight: '8px' }}
|
||||
name='logType'
|
||||
@ -379,7 +379,7 @@ const LogsTable = () => {
|
||||
setLogType(value);
|
||||
}}
|
||||
/>
|
||||
<Button size='small' onClick={refresh} loading={loading}>刷新</Button>
|
||||
<Button size='small' onClick={refresh} loading={loading}>Refresh</Button>
|
||||
<Pagination
|
||||
floated='right'
|
||||
activePage={activePage}
|
||||
|
@ -93,21 +93,21 @@ const OperationSetting = () => {
|
||||
case 'ratio':
|
||||
if (originInputs['ModelRatio'] !== inputs.ModelRatio) {
|
||||
if (!verifyJSON(inputs.ModelRatio)) {
|
||||
showError('模型倍率不是合法的 JSON 字符串');
|
||||
showError('Model rate is not a valid JSON string');
|
||||
return;
|
||||
}
|
||||
await updateOption('ModelRatio', inputs.ModelRatio);
|
||||
}
|
||||
if (originInputs['GroupRatio'] !== inputs.GroupRatio) {
|
||||
if (!verifyJSON(inputs.GroupRatio)) {
|
||||
showError('分组倍率不是合法的 JSON 字符串');
|
||||
showError('Group rate is not a valid JSON string');
|
||||
return;
|
||||
}
|
||||
await updateOption('GroupRatio', inputs.GroupRatio);
|
||||
}
|
||||
if (originInputs['CompletionRatio'] !== inputs.CompletionRatio) {
|
||||
if (!verifyJSON(inputs.CompletionRatio)) {
|
||||
showError('补全倍率不是合法的 JSON 字符串');
|
||||
showError('Completion倍率不是合法的 JSON 字符串');
|
||||
return;
|
||||
}
|
||||
await updateOption('CompletionRatio', inputs.CompletionRatio);
|
||||
@ -149,10 +149,10 @@ const OperationSetting = () => {
|
||||
const res = await API.delete(`/api/log/?target_timestamp=${Date.parse(historyTimestamp) / 1000}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
showSuccess(`${data} 条日志已清理!`);
|
||||
showSuccess(`${data} 条Logs已清理!`);
|
||||
return;
|
||||
}
|
||||
showError('日志清理失败:' + message);
|
||||
showError('Logs清理失败:' + message);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -160,39 +160,39 @@ const OperationSetting = () => {
|
||||
<Grid.Column>
|
||||
<Form loading={loading}>
|
||||
<Header as='h3'>
|
||||
通用设置
|
||||
General Settings
|
||||
</Header>
|
||||
<Form.Group widths={4}>
|
||||
<Form.Input
|
||||
label='充值链接'
|
||||
label='Recharge Link'
|
||||
name='TopUpLink'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.TopUpLink}
|
||||
type='link'
|
||||
placeholder='例如发卡网站的购买链接'
|
||||
placeholder='For example, the purchase link of the card issuing website'
|
||||
/>
|
||||
<Form.Input
|
||||
label='聊天页面链接'
|
||||
label='Chat Page Link'
|
||||
name='ChatLink'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.ChatLink}
|
||||
type='link'
|
||||
placeholder='例如 ChatGPT Next Web 的部署地址'
|
||||
placeholder='For example, the deployment address of ChatGPT Next Web'
|
||||
/>
|
||||
<Form.Input
|
||||
label='单位美元额度'
|
||||
label='Unit Dollar Quota'
|
||||
name='QuotaPerUnit'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaPerUnit}
|
||||
type='number'
|
||||
step='0.01'
|
||||
placeholder='一单位货币能兑换的额度'
|
||||
placeholder='Quota that can be exchanged for one unit of currency'
|
||||
/>
|
||||
<Form.Input
|
||||
label='失败重试次数'
|
||||
label='失败Retry次数'
|
||||
name='RetryTimes'
|
||||
type={'number'}
|
||||
step='1'
|
||||
@ -200,46 +200,46 @@ const OperationSetting = () => {
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.RetryTimes}
|
||||
placeholder='失败重试次数'
|
||||
placeholder='失败Retry次数'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.DisplayInCurrencyEnabled === 'true'}
|
||||
label='以货币形式显示额度'
|
||||
label='Display quota in the form of currency'
|
||||
name='DisplayInCurrencyEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.DisplayTokenStatEnabled === 'true'}
|
||||
label='Billing 相关 API 显示令牌额度而非用户额度'
|
||||
label='Billing Related API displays token quota instead of user quota'
|
||||
name='DisplayTokenStatEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.ApproximateTokenEnabled === 'true'}
|
||||
label='使用近似的方式估算 token 数以减少计算量'
|
||||
label='Estimate the number of tokens in an approximate way to reduce computational load'
|
||||
name='ApproximateTokenEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={() => {
|
||||
submitConfig('general').then();
|
||||
}}>保存通用设置</Form.Button>
|
||||
}}>Save General Settings</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
日志设置
|
||||
LogsSettings
|
||||
</Header>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.LogConsumeEnabled === 'true'}
|
||||
label='启用额度消费日志记录'
|
||||
label='Enable quota consumption log recording'
|
||||
name='LogConsumeEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths={4}>
|
||||
<Form.Input label='目标时间' value={historyTimestamp} type='datetime-local'
|
||||
<Form.Input label='目标Time' value={historyTimestamp} type='datetime-local'
|
||||
name='history_timestamp'
|
||||
onChange={(e, { name, value }) => {
|
||||
setHistoryTimestamp(value);
|
||||
@ -247,139 +247,139 @@ const OperationSetting = () => {
|
||||
</Form.Group>
|
||||
<Form.Button onClick={() => {
|
||||
deleteHistoryLogs().then();
|
||||
}}>清理历史日志</Form.Button>
|
||||
}}>清理历史Logs</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
监控设置
|
||||
Monitoring Settings
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label='最长响应时间'
|
||||
label='Longest Response Time'
|
||||
name='ChannelDisableThreshold'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.ChannelDisableThreshold}
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='单位秒,当运行渠道全部测试时,超过此时间将自动禁用渠道'
|
||||
placeholder='Unit in seconds,When all operating channels are tested,Channels will be automatically disabled if this time is exceeded'
|
||||
/>
|
||||
<Form.Input
|
||||
label='额度提醒阈值'
|
||||
label='Quota reminder threshold'
|
||||
name='QuotaRemindThreshold'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaRemindThreshold}
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='低于此额度时将发送邮件提醒用户'
|
||||
placeholder='Email will be sent to remind users when the quota is below this'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.AutomaticDisableChannelEnabled === 'true'}
|
||||
label='失败时自动禁用渠道'
|
||||
label='Automatically disable the channel when it fails'
|
||||
name='AutomaticDisableChannelEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.AutomaticEnableChannelEnabled === 'true'}
|
||||
label='成功时自动启用渠道'
|
||||
label='成功时自动EnableChannel'
|
||||
name='AutomaticEnableChannelEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={() => {
|
||||
submitConfig('monitor').then();
|
||||
}}>保存监控设置</Form.Button>
|
||||
}}>Save Monitoring Settings</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
额度设置
|
||||
Quota Settings
|
||||
</Header>
|
||||
<Form.Group widths={4}>
|
||||
<Form.Input
|
||||
label='新用户初始额度'
|
||||
label='Initial quota for new users'
|
||||
name='QuotaForNewUser'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaForNewUser}
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='例如:100'
|
||||
placeholder='For example:100'
|
||||
/>
|
||||
<Form.Input
|
||||
label='请求预扣费额度'
|
||||
label='Request for pre-deducted quota'
|
||||
name='PreConsumedQuota'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.PreConsumedQuota}
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='请求结束后多退少补'
|
||||
placeholder='Refund more or less after the request ends'
|
||||
/>
|
||||
<Form.Input
|
||||
label='邀请新用户奖励额度'
|
||||
label='Invite new users to reward quota'
|
||||
name='QuotaForInviter'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaForInviter}
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='例如:2000'
|
||||
placeholder='For example:2000'
|
||||
/>
|
||||
<Form.Input
|
||||
label='新用户使用邀请码奖励额度'
|
||||
label='New user rewards quota using invitation code'
|
||||
name='QuotaForInvitee'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaForInvitee}
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='例如:1000'
|
||||
placeholder='For example:1000'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={() => {
|
||||
submitConfig('quota').then();
|
||||
}}>保存额度设置</Form.Button>
|
||||
}}>Save Quota Settings</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
倍率设置
|
||||
Rate Settings
|
||||
</Header>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label='模型倍率'
|
||||
label='model rate'
|
||||
name='ModelRatio'
|
||||
onChange={handleInputChange}
|
||||
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
autoComplete='new-password'
|
||||
value={inputs.ModelRatio}
|
||||
placeholder='为一个 JSON 文本,键为模型名称,值为倍率'
|
||||
placeholder='Is a JSON text,Key is model name,Value is the rate'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label='补全倍率'
|
||||
label='Completion倍率'
|
||||
name='CompletionRatio'
|
||||
onChange={handleInputChange}
|
||||
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
autoComplete='new-password'
|
||||
value={inputs.CompletionRatio}
|
||||
placeholder='为一个 JSON 文本,键为模型名称,值为倍率,此处的倍率设置是模型补全倍率相较于提示倍率的比例,使用该设置可强制覆盖 One API 的内部比例'
|
||||
placeholder='Is a JSON text,Key is model name,Value is the rate,此处的Rate Settings是ModelCompletion倍率相较于Prompt倍率的比例,使用该Settings可强制覆盖 One API 的内部比例'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label='分组倍率'
|
||||
label='group rate'
|
||||
name='GroupRatio'
|
||||
onChange={handleInputChange}
|
||||
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
autoComplete='new-password'
|
||||
value={inputs.GroupRatio}
|
||||
placeholder='为一个 JSON 文本,键为分组名称,值为倍率'
|
||||
placeholder='Is a JSON text,Key is group name,Value is the rate'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={() => {
|
||||
submitConfig('ratio').then();
|
||||
}}>保存倍率设置</Form.Button>
|
||||
}}>Save Rate Settings</Form.Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
|
@ -99,7 +99,7 @@ const OtherSetting = () => {
|
||||
);
|
||||
const { tag_name, body } = res.data;
|
||||
if (tag_name === process.env.REACT_APP_VERSION) {
|
||||
showSuccess(`已是最新版本:${tag_name}`);
|
||||
showSuccess(`Is the latest version:${tag_name}`);
|
||||
} else {
|
||||
setUpdateData({
|
||||
tag_name: tag_name,
|
||||
@ -113,87 +113,87 @@ const OtherSetting = () => {
|
||||
<Grid columns={1}>
|
||||
<Grid.Column>
|
||||
<Form loading={loading}>
|
||||
<Header as='h3'>通用设置</Header>
|
||||
<Form.Button onClick={checkUpdate}>检查更新</Form.Button>
|
||||
<Header as='h3'>General Settings</Header>
|
||||
<Form.Button onClick={checkUpdate}>Check for updates</Form.Button>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label='公告'
|
||||
placeholder='在此输入新的公告内容,支持 Markdown & HTML 代码'
|
||||
label='Announcement'
|
||||
placeholder='Enter the new announcement content here, supports Markdown & HTML code'
|
||||
value={inputs.Notice}
|
||||
name='Notice'
|
||||
onChange={handleInputChange}
|
||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitNotice}>保存公告</Form.Button>
|
||||
<Form.Button onClick={submitNotice}>Save Announcement</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>个性化设置</Header>
|
||||
<Header as='h3'>Personalization Settings</Header>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
label='系统名称'
|
||||
placeholder='在此输入系统名称'
|
||||
label='System Name'
|
||||
placeholder='Enter the system name here'
|
||||
value={inputs.SystemName}
|
||||
name='SystemName'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitSystemName}>设置系统名称</Form.Button>
|
||||
<Form.Button onClick={submitSystemName}>Set system name</Form.Button>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
label={<label>主题名称(<Link
|
||||
label={<label>主题Name(<Link
|
||||
to='https://github.com/songquanpeng/one-api/blob/main/web/README.md'>当前可用主题</Link>)</label>}
|
||||
placeholder='请输入主题名称'
|
||||
placeholder='请Enter主题Name'
|
||||
value={inputs.Theme}
|
||||
name='Theme'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitTheme}>设置主题(重启生效)</Form.Button>
|
||||
<Form.Button onClick={submitTheme}>Settings主题(重启生效)</Form.Button>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
label='Logo 图片地址'
|
||||
placeholder='在此输入 Logo 图片地址'
|
||||
label='Logo Image URL'
|
||||
placeholder='Enter the Logo image URL here'
|
||||
value={inputs.Logo}
|
||||
name='Logo'
|
||||
type='url'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitLogo}>设置 Logo</Form.Button>
|
||||
<Form.Button onClick={submitLogo}>Settings Logo</Form.Button>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label='首页内容'
|
||||
placeholder='在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'
|
||||
label='Home Page Content'
|
||||
placeholder='Enter the homepage content here, supports Markdown & HTML code. Once set, the status information of the homepage will not be displayed. If a link is entered, it will be used as the src attribute of the iframe, allowing you to set any webpage as the homepage.。'
|
||||
value={inputs.HomePageContent}
|
||||
name='HomePageContent'
|
||||
onChange={handleInputChange}
|
||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={() => submitOption('HomePageContent')}>保存首页内容</Form.Button>
|
||||
<Form.Button onClick={() => submitOption('HomePageContent')}>Save Home Page Content</Form.Button>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label='关于'
|
||||
placeholder='在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'
|
||||
label='About'
|
||||
placeholder='Enter new about content here, supports Markdown & HTML code. If a link is entered, it will be used as the src attribute of the iframe, allowing you to set any webpage as the about page.。'
|
||||
value={inputs.About}
|
||||
name='About'
|
||||
onChange={handleInputChange}
|
||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitAbout}>保存关于</Form.Button>
|
||||
<Form.Button onClick={submitAbout}>Save About</Form.Button>
|
||||
<Message>移除 One API
|
||||
的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。</Message>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
label='页脚'
|
||||
placeholder='在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'
|
||||
label='Footer'
|
||||
placeholder='Enter the new footer here, leave blank to use the default footer, supports HTML code.'
|
||||
value={inputs.Footer}
|
||||
name='Footer'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitFooter}>设置页脚</Form.Button>
|
||||
<Form.Button onClick={submitFooter}>Set Footer</Form.Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
<Modal
|
||||
@ -201,16 +201,16 @@ const OtherSetting = () => {
|
||||
onOpen={() => setShowUpdateModal(true)}
|
||||
open={showUpdateModal}
|
||||
>
|
||||
<Modal.Header>新版本:{updateData.tag_name}</Modal.Header>
|
||||
<Modal.Header>New Version:{updateData.tag_name}</Modal.Header>
|
||||
<Modal.Content>
|
||||
<Modal.Description>
|
||||
<div dangerouslySetInnerHTML={{ __html: updateData.content }}></div>
|
||||
</Modal.Description>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => setShowUpdateModal(false)}>关闭</Button>
|
||||
<Button onClick={() => setShowUpdateModal(false)}>Close</Button>
|
||||
<Button
|
||||
content='详情'
|
||||
content='Details'
|
||||
onClick={() => {
|
||||
setShowUpdateModal(false);
|
||||
openGitHubRelease();
|
||||
|
@ -53,7 +53,7 @@ const PasswordResetConfirm = () => {
|
||||
let password = res.data.data;
|
||||
setNewPassword(password);
|
||||
await copy(password);
|
||||
showNotice(`新密码已复制到剪贴板:${password}`);
|
||||
showNotice(`New password has been copied to the clipboard:${password}`);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -64,7 +64,7 @@ const PasswordResetConfirm = () => {
|
||||
<Grid textAlign='center' style={{ marginTop: '48px' }}>
|
||||
<Grid.Column style={{ maxWidth: 450 }}>
|
||||
<Header as='h2' color='' textAlign='center'>
|
||||
<Image src='/logo.png' /> 密码重置确认
|
||||
<Image src='/logo.png' /> Password reset confirmation
|
||||
</Header>
|
||||
<Form size='large'>
|
||||
<Segment>
|
||||
@ -72,7 +72,7 @@ const PasswordResetConfirm = () => {
|
||||
fluid
|
||||
icon='mail'
|
||||
iconPosition='left'
|
||||
placeholder='邮箱地址'
|
||||
placeholder='Email address'
|
||||
name='email'
|
||||
value={email}
|
||||
readOnly
|
||||
@ -82,14 +82,14 @@ const PasswordResetConfirm = () => {
|
||||
fluid
|
||||
icon='lock'
|
||||
iconPosition='left'
|
||||
placeholder='新密码'
|
||||
placeholder='New password'
|
||||
name='newPassword'
|
||||
value={newPassword}
|
||||
readOnly
|
||||
onClick={(e) => {
|
||||
e.target.select();
|
||||
navigator.clipboard.writeText(newPassword);
|
||||
showNotice(`密码已复制到剪贴板:${newPassword}`);
|
||||
showNotice(`Password has been copied to the clipboard:${newPassword}`);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -101,7 +101,7 @@ const PasswordResetConfirm = () => {
|
||||
loading={loading}
|
||||
disabled={disableButton}
|
||||
>
|
||||
{disableButton ? `密码重置完成` : '提交'}
|
||||
{disableButton ? `Password reset complete` : 'Submit'}
|
||||
</Button>
|
||||
</Segment>
|
||||
</Form>
|
||||
|
@ -49,7 +49,7 @@ const PasswordResetForm = () => {
|
||||
setDisableButton(true);
|
||||
if (!email) return;
|
||||
if (turnstileEnabled && turnstileToken === '') {
|
||||
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
|
||||
showInfo('Please try again in a few seconds, Turnstile is checking the user environment!');
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@ -58,7 +58,7 @@ const PasswordResetForm = () => {
|
||||
);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('重置邮件发送成功,请检查邮箱!');
|
||||
showSuccess('Reset email sent successfully, please check your email!');
|
||||
setInputs({ ...inputs, email: '' });
|
||||
} else {
|
||||
showError(message);
|
||||
@ -70,7 +70,7 @@ const PasswordResetForm = () => {
|
||||
<Grid textAlign='center' style={{ marginTop: '48px' }}>
|
||||
<Grid.Column style={{ maxWidth: 450 }}>
|
||||
<Header as='h2' color='' textAlign='center'>
|
||||
<Image src='/logo.png' /> 密码重置
|
||||
<Image src='/logo.png' /> Password reset
|
||||
</Header>
|
||||
<Form size='large'>
|
||||
<Segment>
|
||||
@ -78,7 +78,7 @@ const PasswordResetForm = () => {
|
||||
fluid
|
||||
icon='mail'
|
||||
iconPosition='left'
|
||||
placeholder='邮箱地址'
|
||||
placeholder='Email address'
|
||||
name='email'
|
||||
value={email}
|
||||
onChange={handleChange}
|
||||
@ -101,7 +101,7 @@ const PasswordResetForm = () => {
|
||||
loading={loading}
|
||||
disabled={disableButton}
|
||||
>
|
||||
{disableButton ? `重试 (${countdown})` : '提交'}
|
||||
{disableButton ? `Retry (${countdown})` : 'Submit'}
|
||||
</Button>
|
||||
</Segment>
|
||||
</Form>
|
||||
|
@ -65,7 +65,7 @@ const PersonalSetting = () => {
|
||||
setSystemToken(data);
|
||||
setAffLink("");
|
||||
await copy(data);
|
||||
showSuccess(`令牌已重置并已复制到剪贴板`);
|
||||
showSuccess(`Token has been reset and copied to the clipboard`);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -79,7 +79,7 @@ const PersonalSetting = () => {
|
||||
setAffLink(link);
|
||||
setSystemToken("");
|
||||
await copy(link);
|
||||
showSuccess(`邀请链接已复制到剪切板`);
|
||||
showSuccess(`Invitation link has been copied to the clipboard`);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -88,18 +88,18 @@ const PersonalSetting = () => {
|
||||
const handleAffLinkClick = async (e) => {
|
||||
e.target.select();
|
||||
await copy(e.target.value);
|
||||
showSuccess(`邀请链接已复制到剪切板`);
|
||||
showSuccess(`Invitation link has been copied to the clipboard`);
|
||||
};
|
||||
|
||||
const handleSystemTokenClick = async (e) => {
|
||||
e.target.select();
|
||||
await copy(e.target.value);
|
||||
showSuccess(`系统令牌已复制到剪切板`);
|
||||
showSuccess(`System token has been copied to the clipboard`);
|
||||
};
|
||||
|
||||
const deleteAccount = async () => {
|
||||
if (inputs.self_account_deletion_confirmation !== userState.user.username) {
|
||||
showError('请输入你的账户名以确认删除!');
|
||||
showError('Please enter your account name to confirm deletion!');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ const PersonalSetting = () => {
|
||||
const { success, message } = res.data;
|
||||
|
||||
if (success) {
|
||||
showSuccess('账户已删除!');
|
||||
showSuccess('Account has been deleted!');
|
||||
await API.get('/api/user/logout');
|
||||
userDispatch({ type: 'logout' });
|
||||
localStorage.removeItem('user');
|
||||
@ -124,7 +124,7 @@ const PersonalSetting = () => {
|
||||
);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('微信账户绑定成功!');
|
||||
showSuccess('WeChat account binding successful!');
|
||||
setShowWeChatBindModal(false);
|
||||
} else {
|
||||
showError(message);
|
||||
@ -135,7 +135,7 @@ const PersonalSetting = () => {
|
||||
setDisableButton(true);
|
||||
if (inputs.email === '') return;
|
||||
if (turnstileEnabled && turnstileToken === '') {
|
||||
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
|
||||
showInfo('Please try again in a few seconds, Turnstile is checking the user environment!');
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@ -144,7 +144,7 @@ const PersonalSetting = () => {
|
||||
);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('验证码发送成功,请检查邮箱!');
|
||||
showSuccess('Verification code sent successfully, please check your email!');
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -159,7 +159,7 @@ const PersonalSetting = () => {
|
||||
);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('邮箱账户绑定成功!');
|
||||
showSuccess('Email account binding successful!');
|
||||
setShowEmailBindModal(false);
|
||||
} else {
|
||||
showError(message);
|
||||
@ -169,18 +169,18 @@ const PersonalSetting = () => {
|
||||
|
||||
return (
|
||||
<div style={{ lineHeight: '40px' }}>
|
||||
<Header as='h3'>通用设置</Header>
|
||||
<Header as='h3'>General Settings</Header>
|
||||
<Message>
|
||||
注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。
|
||||
Note that, The token generated here is used for system management,Not for requesting OpenAI related services,Please be aware。
|
||||
</Message>
|
||||
<Button as={Link} to={`/user/edit/`}>
|
||||
更新个人信息
|
||||
Update Personal Information
|
||||
</Button>
|
||||
<Button onClick={generateAccessToken}>生成系统访问令牌</Button>
|
||||
<Button onClick={getAffLink}>复制邀请链接</Button>
|
||||
<Button onClick={generateAccessToken}>Generate system access token</Button>
|
||||
<Button onClick={getAffLink}>Copy invitation link</Button>
|
||||
<Button onClick={() => {
|
||||
setShowAccountDeleteModal(true);
|
||||
}}>删除个人账户</Button>
|
||||
}}>Delete个人账户</Button>
|
||||
|
||||
{systemToken && (
|
||||
<Form.Input
|
||||
@ -201,7 +201,7 @@ const PersonalSetting = () => {
|
||||
/>
|
||||
)}
|
||||
<Divider />
|
||||
<Header as='h3'>账号绑定</Header>
|
||||
<Header as='h3'>Account binding</Header>
|
||||
{
|
||||
status.wechat_login && (
|
||||
<Button
|
||||
@ -209,7 +209,7 @@ const PersonalSetting = () => {
|
||||
setShowWeChatBindModal(true);
|
||||
}}
|
||||
>
|
||||
绑定微信账号
|
||||
Bind WeChat Account
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@ -224,19 +224,19 @@ const PersonalSetting = () => {
|
||||
<Image src={status.wechat_qrcode} fluid />
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<p>
|
||||
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
|
||||
Scan the QR code with WeChat, follow the official account and enter 'verification code' to get the verification code (valid within three minutes)
|
||||
</p>
|
||||
</div>
|
||||
<Form size='large'>
|
||||
<Form.Input
|
||||
fluid
|
||||
placeholder='验证码'
|
||||
placeholder='Verification code'
|
||||
name='wechat_verification_code'
|
||||
value={inputs.wechat_verification_code}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Button color='' fluid size='large' onClick={bindWeChat}>
|
||||
绑定
|
||||
Bind
|
||||
</Button>
|
||||
</Form>
|
||||
</Modal.Description>
|
||||
@ -244,12 +244,12 @@ const PersonalSetting = () => {
|
||||
</Modal>
|
||||
{
|
||||
status.github_oauth && (
|
||||
<Button onClick={()=>{onGitHubOAuthClicked(status.github_client_id)}}>绑定 GitHub 账号</Button>
|
||||
<Button onClick={()=>{onGitHubOAuthClicked(status.github_client_id)}}>Bind GitHub Account</Button>
|
||||
)
|
||||
}
|
||||
{
|
||||
status.lark_client_id && (
|
||||
<Button onClick={()=>{onLarkOAuthClicked(status.lark_client_id)}}>绑定飞书账号</Button>
|
||||
<Button onClick={()=>{onLarkOAuthClicked(status.lark_client_id)}}>Bind飞书账号</Button>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
@ -257,7 +257,7 @@ const PersonalSetting = () => {
|
||||
setShowEmailBindModal(true);
|
||||
}}
|
||||
>
|
||||
绑定邮箱地址
|
||||
Bind email address
|
||||
</Button>
|
||||
<Modal
|
||||
onClose={() => setShowEmailBindModal(false)}
|
||||
@ -266,25 +266,25 @@ const PersonalSetting = () => {
|
||||
size={'tiny'}
|
||||
style={{ maxWidth: '450px' }}
|
||||
>
|
||||
<Modal.Header>绑定邮箱地址</Modal.Header>
|
||||
<Modal.Header>Bind email address</Modal.Header>
|
||||
<Modal.Content>
|
||||
<Modal.Description>
|
||||
<Form size='large'>
|
||||
<Form.Input
|
||||
fluid
|
||||
placeholder='输入邮箱地址'
|
||||
placeholder='Enter email address'
|
||||
onChange={handleInputChange}
|
||||
name='email'
|
||||
type='email'
|
||||
action={
|
||||
<Button onClick={sendVerificationCode} disabled={disableButton || loading}>
|
||||
{disableButton ? `重新发送(${countdown})` : '获取验证码'}
|
||||
{disableButton ? `Resend(${countdown})` : 'Get verification code'}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Form.Input
|
||||
fluid
|
||||
placeholder='验证码'
|
||||
placeholder='Verification code'
|
||||
name='email_verification_code'
|
||||
value={inputs.email_verification_code}
|
||||
onChange={handleInputChange}
|
||||
@ -307,7 +307,7 @@ const PersonalSetting = () => {
|
||||
onClick={bindEmail}
|
||||
loading={loading}
|
||||
>
|
||||
确认绑定
|
||||
Confirm binding
|
||||
</Button>
|
||||
<div style={{ width: '1rem' }}></div>
|
||||
<Button
|
||||
@ -315,7 +315,7 @@ const PersonalSetting = () => {
|
||||
size='large'
|
||||
onClick={() => setShowEmailBindModal(false)}
|
||||
>
|
||||
取消
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
@ -329,14 +329,14 @@ const PersonalSetting = () => {
|
||||
size={'tiny'}
|
||||
style={{ maxWidth: '450px' }}
|
||||
>
|
||||
<Modal.Header>危险操作</Modal.Header>
|
||||
<Modal.Header>Dangerous operation</Modal.Header>
|
||||
<Modal.Content>
|
||||
<Message>您正在删除自己的帐户,将清空所有数据且不可恢复</Message>
|
||||
<Message>You are deleting your own account, all data will be cleared and cannot be recovered</Message>
|
||||
<Modal.Description>
|
||||
<Form size='large'>
|
||||
<Form.Input
|
||||
fluid
|
||||
placeholder={`输入你的账户名 ${userState?.user?.username} 以确认删除`}
|
||||
placeholder={`Enter your account name ${userState?.user?.username} To confirm deletion`}
|
||||
name='self_account_deletion_confirmation'
|
||||
value={inputs.self_account_deletion_confirmation}
|
||||
onChange={handleInputChange}
|
||||
@ -359,7 +359,7 @@ const PersonalSetting = () => {
|
||||
onClick={deleteAccount}
|
||||
loading={loading}
|
||||
>
|
||||
确认删除
|
||||
Confirm deletion
|
||||
</Button>
|
||||
<div style={{ width: '1rem' }}></div>
|
||||
<Button
|
||||
@ -367,7 +367,7 @@ const PersonalSetting = () => {
|
||||
size='large'
|
||||
onClick={() => setShowAccountDeleteModal(false)}
|
||||
>
|
||||
取消
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
@ -17,13 +17,13 @@ function renderTimestamp(timestamp) {
|
||||
function renderStatus(status) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return <Label basic color='green'>未使用</Label>;
|
||||
return <Label basic color='green'>Not used</Label>;
|
||||
case 2:
|
||||
return <Label basic color='red'> 已禁用 </Label>;
|
||||
return <Label basic color='red'> Disabled </Label>;
|
||||
case 3:
|
||||
return <Label basic color='grey'> 已使用 </Label>;
|
||||
return <Label basic color='grey'> Used </Label>;
|
||||
default:
|
||||
return <Label basic color='black'> 未知状态 </Label>;
|
||||
return <Label basic color='black'> Unknown status </Label>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ const RedemptionsTable = () => {
|
||||
}
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('操作成功完成!');
|
||||
showSuccess('Operation successfully completed!');
|
||||
let redemption = res.data.data;
|
||||
let newRedemptions = [...redemptions];
|
||||
let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
|
||||
@ -152,7 +152,7 @@ const RedemptionsTable = () => {
|
||||
icon='search'
|
||||
fluid
|
||||
iconPosition='left'
|
||||
placeholder='搜索兑换码的 ID 和名称 ...'
|
||||
placeholder='Search for the ID and name of the redemption code ...'
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={handleKeywordChange}
|
||||
@ -176,7 +176,7 @@ const RedemptionsTable = () => {
|
||||
sortRedemption('name');
|
||||
}}
|
||||
>
|
||||
名称
|
||||
Name
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -184,7 +184,7 @@ const RedemptionsTable = () => {
|
||||
sortRedemption('status');
|
||||
}}
|
||||
>
|
||||
状态
|
||||
Status
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -192,7 +192,7 @@ const RedemptionsTable = () => {
|
||||
sortRedemption('quota');
|
||||
}}
|
||||
>
|
||||
额度
|
||||
Quota
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -200,7 +200,7 @@ const RedemptionsTable = () => {
|
||||
sortRedemption('created_time');
|
||||
}}
|
||||
>
|
||||
创建时间
|
||||
Creation time
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -208,9 +208,9 @@ const RedemptionsTable = () => {
|
||||
sortRedemption('redeemed_time');
|
||||
}}
|
||||
>
|
||||
兑换时间
|
||||
Redemption time
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell>操作</Table.HeaderCell>
|
||||
<Table.HeaderCell>Operation</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
|
||||
@ -225,11 +225,11 @@ const RedemptionsTable = () => {
|
||||
return (
|
||||
<Table.Row key={redemption.id}>
|
||||
<Table.Cell>{redemption.id}</Table.Cell>
|
||||
<Table.Cell>{redemption.name ? redemption.name : '无'}</Table.Cell>
|
||||
<Table.Cell>{redemption.name ? redemption.name : 'None'}</Table.Cell>
|
||||
<Table.Cell>{renderStatus(redemption.status)}</Table.Cell>
|
||||
<Table.Cell>{renderQuota(redemption.quota)}</Table.Cell>
|
||||
<Table.Cell>{renderTimestamp(redemption.created_time)}</Table.Cell>
|
||||
<Table.Cell>{redemption.redeemed_time ? renderTimestamp(redemption.redeemed_time) : "尚未兑换"} </Table.Cell>
|
||||
<Table.Cell>{redemption.redeemed_time ? renderTimestamp(redemption.redeemed_time) : "Not yet redeemed"} </Table.Cell>
|
||||
<Table.Cell>
|
||||
<div>
|
||||
<Button
|
||||
@ -237,19 +237,19 @@ const RedemptionsTable = () => {
|
||||
positive
|
||||
onClick={async () => {
|
||||
if (await copy(redemption.key)) {
|
||||
showSuccess('已复制到剪贴板!');
|
||||
showSuccess('Copied to clipboard!');
|
||||
} else {
|
||||
showWarning('无法复制到剪贴板,请手动复制,已将兑换码填入搜索框。')
|
||||
showWarning('Unable to copy to clipboard, please copy manually. The redemption code has been filled in the search box.')
|
||||
setSearchKeyword(redemption.key);
|
||||
}
|
||||
}}
|
||||
>
|
||||
复制
|
||||
Copy
|
||||
</Button>
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative>
|
||||
删除
|
||||
Delete
|
||||
</Button>
|
||||
}
|
||||
on='click'
|
||||
@ -262,7 +262,7 @@ const RedemptionsTable = () => {
|
||||
manageRedemption(redemption.id, 'delete', idx);
|
||||
}}
|
||||
>
|
||||
确认删除
|
||||
Confirm deletion
|
||||
</Button>
|
||||
</Popup>
|
||||
<Button
|
||||
@ -276,14 +276,14 @@ const RedemptionsTable = () => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{redemption.status === 1 ? '禁用' : '启用'}
|
||||
{redemption.status === 1 ? 'Disable' : 'Enable'}
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
as={Link}
|
||||
to={'/redemption/edit/' + redemption.id}
|
||||
>
|
||||
编辑
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
@ -296,7 +296,7 @@ const RedemptionsTable = () => {
|
||||
<Table.Row>
|
||||
<Table.HeaderCell colSpan='8'>
|
||||
<Button size='small' as={Link} to='/redemption/add' loading={loading}>
|
||||
添加新的兑换码
|
||||
Add new redemption code
|
||||
</Button>
|
||||
<Pagination
|
||||
floated='right'
|
||||
|
@ -46,16 +46,16 @@ const RegisterForm = () => {
|
||||
|
||||
async function handleSubmit(e) {
|
||||
if (password.length < 8) {
|
||||
showInfo('密码长度不得小于 8 位!');
|
||||
showInfo('Password length must not be less than 8 characters!');
|
||||
return;
|
||||
}
|
||||
if (password !== password2) {
|
||||
showInfo('两次输入的密码不一致');
|
||||
showInfo('The two passwords entered do not match');
|
||||
return;
|
||||
}
|
||||
if (username && password) {
|
||||
if (turnstileEnabled && turnstileToken === '') {
|
||||
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
|
||||
showInfo('Please try again in a few seconds, Turnstile is checking the user environment!');
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@ -70,7 +70,7 @@ const RegisterForm = () => {
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
navigate('/login');
|
||||
showSuccess('注册成功!');
|
||||
showSuccess('Registration successful!');
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -81,7 +81,7 @@ const RegisterForm = () => {
|
||||
const sendVerificationCode = async () => {
|
||||
if (inputs.email === '') return;
|
||||
if (turnstileEnabled && turnstileToken === '') {
|
||||
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
|
||||
showInfo('Please try again in a few seconds, Turnstile is checking the user environment!');
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@ -90,7 +90,7 @@ const RegisterForm = () => {
|
||||
);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('验证码发送成功,请检查你的邮箱!');
|
||||
showSuccess('Verification code sent successfully, please check your email!');
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -101,7 +101,7 @@ const RegisterForm = () => {
|
||||
<Grid textAlign='center' style={{ marginTop: '48px' }}>
|
||||
<Grid.Column style={{ maxWidth: 450 }}>
|
||||
<Header as='h2' color='' textAlign='center'>
|
||||
<Image src={logo} /> 新用户注册
|
||||
<Image src={logo} /> New User Registration
|
||||
</Header>
|
||||
<Form size='large'>
|
||||
<Segment>
|
||||
@ -109,7 +109,7 @@ const RegisterForm = () => {
|
||||
fluid
|
||||
icon='user'
|
||||
iconPosition='left'
|
||||
placeholder='输入用户名,最长 12 位'
|
||||
placeholder='Enter username, up to 12 characters'
|
||||
onChange={handleChange}
|
||||
name='username'
|
||||
/>
|
||||
@ -117,7 +117,7 @@ const RegisterForm = () => {
|
||||
fluid
|
||||
icon='lock'
|
||||
iconPosition='left'
|
||||
placeholder='输入密码,最短 8 位,最长 20 位'
|
||||
placeholder='Enter password, at least 8 characters and up to 20 characters'
|
||||
onChange={handleChange}
|
||||
name='password'
|
||||
type='password'
|
||||
@ -126,7 +126,7 @@ const RegisterForm = () => {
|
||||
fluid
|
||||
icon='lock'
|
||||
iconPosition='left'
|
||||
placeholder='输入密码,最短 8 位,最长 20 位'
|
||||
placeholder='Enter password, at least 8 characters and up to 20 characters'
|
||||
onChange={handleChange}
|
||||
name='password2'
|
||||
type='password'
|
||||
@ -137,13 +137,13 @@ const RegisterForm = () => {
|
||||
fluid
|
||||
icon='mail'
|
||||
iconPosition='left'
|
||||
placeholder='输入邮箱地址'
|
||||
placeholder='Enter email address'
|
||||
onChange={handleChange}
|
||||
name='email'
|
||||
type='email'
|
||||
action={
|
||||
<Button onClick={sendVerificationCode} disabled={loading}>
|
||||
获取验证码
|
||||
Get verification code
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
@ -151,7 +151,7 @@ const RegisterForm = () => {
|
||||
fluid
|
||||
icon='lock'
|
||||
iconPosition='left'
|
||||
placeholder='输入验证码'
|
||||
placeholder='Enter Verification Code'
|
||||
onChange={handleChange}
|
||||
name='verification_code'
|
||||
/>
|
||||
@ -176,14 +176,14 @@ const RegisterForm = () => {
|
||||
onClick={handleSubmit}
|
||||
loading={loading}
|
||||
>
|
||||
注册
|
||||
Sign up
|
||||
</Button>
|
||||
</Segment>
|
||||
</Form>
|
||||
<Message>
|
||||
已有账户?
|
||||
Already have an account?
|
||||
<Link to='/login' className='btn btn-link'>
|
||||
点击登录
|
||||
Click to log in
|
||||
</Link>
|
||||
</Message>
|
||||
</Grid.Column>
|
||||
|
@ -260,25 +260,25 @@ const SystemSetting = () => {
|
||||
<Grid columns={1}>
|
||||
<Grid.Column>
|
||||
<Form loading={loading}>
|
||||
<Header as='h3'>通用设置</Header>
|
||||
<Header as='h3'>General Settings</Header>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
label='服务器地址'
|
||||
placeholder='例如:https://yourdomain.com'
|
||||
label='Server Address'
|
||||
placeholder='For example:https://yourdomain.com'
|
||||
value={inputs.ServerAddress}
|
||||
name='ServerAddress'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitServerAddress}>
|
||||
更新服务器地址
|
||||
Update Server Address
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>配置登录注册</Header>
|
||||
<Header as='h3'>Configure Login/Registration</Header>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.PasswordLoginEnabled === 'true'}
|
||||
label='允许通过密码进行登录'
|
||||
label='Allow login via password'
|
||||
name='PasswordLoginEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
@ -292,10 +292,10 @@ const SystemSetting = () => {
|
||||
>
|
||||
<Modal.Header>警告</Modal.Header>
|
||||
<Modal.Content>
|
||||
<p>取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?</p>
|
||||
<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>
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => setShowPasswordWarningModal(false)}>取消</Button>
|
||||
<Button onClick={() => setShowPasswordWarningModal(false)}>Cancel</Button>
|
||||
<Button
|
||||
color='yellow'
|
||||
onClick={async () => {
|
||||
@ -310,25 +310,25 @@ const SystemSetting = () => {
|
||||
}
|
||||
<Form.Checkbox
|
||||
checked={inputs.PasswordRegisterEnabled === 'true'}
|
||||
label='允许通过密码进行注册'
|
||||
label='Allow registration via password'
|
||||
name='PasswordRegisterEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.EmailVerificationEnabled === 'true'}
|
||||
label='通过密码注册时需要进行邮箱验证'
|
||||
label='Email verification is required when registering via password'
|
||||
name='EmailVerificationEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.GitHubOAuthEnabled === 'true'}
|
||||
label='允许通过 GitHub 账户登录 & 注册'
|
||||
label='Allow login & registration via GitHub account'
|
||||
name='GitHubOAuthEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.WeChatAuthEnabled === 'true'}
|
||||
label='允许通过微信登录 & 注册'
|
||||
label='Allow login & registration via WeChat'
|
||||
name='WeChatAuthEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
@ -336,13 +336,13 @@ const SystemSetting = () => {
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.RegisterEnabled === 'true'}
|
||||
label='允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)'
|
||||
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}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.TurnstileCheckEnabled === 'true'}
|
||||
label='启用 Turnstile 用户校验'
|
||||
label='Enable Turnstile user verification'
|
||||
name='TurnstileCheckEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
@ -350,11 +350,11 @@ const SystemSetting = () => {
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
配置邮箱域名白名单
|
||||
<Header.Subheader>用以防止恶意用户利用临时邮箱批量注册</Header.Subheader>
|
||||
<Header.Subheader>用以防止恶意Users利用临时邮箱批量Sign up</Header.Subheader>
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Checkbox
|
||||
label='启用邮箱域名白名单'
|
||||
label='Enable邮箱域名白名单'
|
||||
name='EmailDomainRestrictionEnabled'
|
||||
onChange={handleInputChange}
|
||||
checked={inputs.EmailDomainRestrictionEnabled === 'true'}
|
||||
@ -387,79 +387,79 @@ const SystemSetting = () => {
|
||||
}
|
||||
}}
|
||||
autoComplete='new-password'
|
||||
placeholder='输入新的允许的邮箱域名'
|
||||
placeholder='Enter新的允许的邮箱域名'
|
||||
value={restrictedDomainInput}
|
||||
onChange={(e, { value }) => {
|
||||
setRestrictedDomainInput(value);
|
||||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单设置</Form.Button>
|
||||
<Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单Settings</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
配置 SMTP
|
||||
<Header.Subheader>用以支持系统的邮件发送</Header.Subheader>
|
||||
Configure SMTP
|
||||
<Header.Subheader>To support the system email sending</Header.Subheader>
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label='SMTP 服务器地址'
|
||||
label='SMTP Server Address'
|
||||
name='SMTPServer'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.SMTPServer}
|
||||
placeholder='例如:smtp.qq.com'
|
||||
placeholder='For example: smtp.qq.com'
|
||||
/>
|
||||
<Form.Input
|
||||
label='SMTP 端口'
|
||||
label='SMTP Port'
|
||||
name='SMTPPort'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.SMTPPort}
|
||||
placeholder='默认: 587'
|
||||
placeholder='Default: 587'
|
||||
/>
|
||||
<Form.Input
|
||||
label='SMTP 账户'
|
||||
label='SMTP Account'
|
||||
name='SMTPAccount'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.SMTPAccount}
|
||||
placeholder='通常是邮箱地址'
|
||||
placeholder='Usually an email address'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label='SMTP 发送者邮箱'
|
||||
label='SMTP Sender email'
|
||||
name='SMTPFrom'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.SMTPFrom}
|
||||
placeholder='通常和邮箱地址保持一致'
|
||||
placeholder='Usually consistent with the email address'
|
||||
/>
|
||||
<Form.Input
|
||||
label='SMTP 访问凭证'
|
||||
label='SMTP Access Credential'
|
||||
name='SMTPToken'
|
||||
onChange={handleInputChange}
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
checked={inputs.RegisterEnabled === 'true'}
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
placeholder='Sensitive information will not be displayed in the frontend'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button>
|
||||
<Form.Button onClick={submitSMTP}>Save SMTP Settings</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
配置 GitHub OAuth App
|
||||
Configure GitHub OAuth App
|
||||
<Header.Subheader>
|
||||
用以支持通过 GitHub 进行登录注册,
|
||||
To support login & registration via GitHub,
|
||||
<a href='https://github.com/settings/developers' target='_blank'>
|
||||
点击此处
|
||||
Click here
|
||||
</a>
|
||||
管理你的 GitHub OAuth App
|
||||
Manage your GitHub OAuth App
|
||||
</Header.Subheader>
|
||||
</Header>
|
||||
<Message>
|
||||
Homepage URL 填 <code>{inputs.ServerAddress}</code>
|
||||
,Authorization callback URL 填{' '}
|
||||
Fill in the Homepage URL <code>{inputs.ServerAddress}</code>
|
||||
,Fill in the Authorization callback URL{' '}
|
||||
<code>{`${inputs.ServerAddress}/oauth/github`}</code>
|
||||
</Message>
|
||||
<Form.Group widths={3}>
|
||||
@ -469,7 +469,7 @@ const SystemSetting = () => {
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.GitHubClientId}
|
||||
placeholder='输入你注册的 GitHub OAuth APP 的 ID'
|
||||
placeholder='Enter your registered GitHub OAuth APP ID'
|
||||
/>
|
||||
<Form.Input
|
||||
label='GitHub Client Secret'
|
||||
@ -478,21 +478,21 @@ const SystemSetting = () => {
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
value={inputs.GitHubClientSecret}
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
placeholder='Sensitive information will not be displayed in the frontend'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitGitHubOAuth}>
|
||||
保存 GitHub OAuth 设置
|
||||
Save GitHub OAuth Settings
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
配置飞书授权登录
|
||||
配置飞书授权Log in
|
||||
<Header.Subheader>
|
||||
用以支持通过飞书进行登录注册,
|
||||
用以支持通过飞书进行Log inSign up,
|
||||
<a href='https://open.feishu.cn/app' target='_blank'>
|
||||
点击此处
|
||||
Click here
|
||||
</a>
|
||||
管理你的飞书应用
|
||||
Management你的飞书应用
|
||||
</Header.Subheader>
|
||||
</Header>
|
||||
<Message>
|
||||
@ -507,7 +507,7 @@ const SystemSetting = () => {
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.LarkClientId}
|
||||
placeholder='输入 App ID'
|
||||
placeholder='Enter App ID'
|
||||
/>
|
||||
<Form.Input
|
||||
label='App Secret'
|
||||
@ -516,55 +516,55 @@ const SystemSetting = () => {
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
value={inputs.LarkClientSecret}
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
placeholder='Sensitive information will not be displayed in the frontend'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitLarkOAuth}>
|
||||
保存飞书 OAuth 设置
|
||||
保存飞书 OAuth Settings
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
配置 WeChat Server
|
||||
Configure WeChat Server
|
||||
<Header.Subheader>
|
||||
用以支持通过微信进行登录注册,
|
||||
To support login & registration via WeChat,
|
||||
<a
|
||||
href='https://github.com/songquanpeng/wechat-server'
|
||||
target='_blank'
|
||||
>
|
||||
点击此处
|
||||
Click here
|
||||
</a>
|
||||
了解 WeChat Server
|
||||
Learn about WeChat Server
|
||||
</Header.Subheader>
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label='WeChat Server 服务器地址'
|
||||
label='WeChat Server Server Address'
|
||||
name='WeChatServerAddress'
|
||||
placeholder='例如:https://yourdomain.com'
|
||||
placeholder='For example:https://yourdomain.com'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.WeChatServerAddress}
|
||||
/>
|
||||
<Form.Input
|
||||
label='WeChat Server 访问凭证'
|
||||
label='WeChat Server Access Credential'
|
||||
name='WeChatServerToken'
|
||||
type='password'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.WeChatServerToken}
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
placeholder='Sensitive information will not be displayed in the frontend'
|
||||
/>
|
||||
<Form.Input
|
||||
label='微信公众号二维码图片链接'
|
||||
label='WeChat Public Account QR Code Image Link'
|
||||
name='WeChatAccountQRCodeImageURL'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.WeChatAccountQRCodeImageURL}
|
||||
placeholder='输入一个图片链接'
|
||||
placeholder='Enter an image link'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitWeChat}>
|
||||
保存 WeChat Server 设置
|
||||
Save WeChat Server Settings
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
@ -575,7 +575,7 @@ const SystemSetting = () => {
|
||||
href='https://github.com/songquanpeng/message-pusher'
|
||||
target='_blank'
|
||||
>
|
||||
点击此处
|
||||
Click here
|
||||
</a>
|
||||
了解 Message Pusher
|
||||
</Header.Subheader>
|
||||
@ -584,7 +584,7 @@ const SystemSetting = () => {
|
||||
<Form.Input
|
||||
label='Message Pusher 推送地址'
|
||||
name='MessagePusherAddress'
|
||||
placeholder='例如:https://msgpusher.com/push/your_username'
|
||||
placeholder='For example:https://msgpusher.com/push/your_username'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.MessagePusherAddress}
|
||||
@ -596,21 +596,21 @@ const SystemSetting = () => {
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.MessagePusherToken}
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
placeholder='Sensitive information will not be displayed in the frontend'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitMessagePusher}>
|
||||
保存 Message Pusher 设置
|
||||
保存 Message Pusher Settings
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
配置 Turnstile
|
||||
Configure Turnstile
|
||||
<Header.Subheader>
|
||||
用以支持用户校验,
|
||||
To support user verification,
|
||||
<a href='https://dash.cloudflare.com/' target='_blank'>
|
||||
点击此处
|
||||
Click here
|
||||
</a>
|
||||
管理你的 Turnstile Sites,推荐选择 Invisible Widget Type
|
||||
Manage your Turnstile Sites, recommend selecting Invisible Widget Type
|
||||
</Header.Subheader>
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
@ -620,7 +620,7 @@ const SystemSetting = () => {
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.TurnstileSiteKey}
|
||||
placeholder='输入你注册的 Turnstile Site Key'
|
||||
placeholder='Enter your registered Turnstile Site Key'
|
||||
/>
|
||||
<Form.Input
|
||||
label='Turnstile Secret Key'
|
||||
@ -629,11 +629,11 @@ const SystemSetting = () => {
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
value={inputs.TurnstileSecretKey}
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
placeholder='Sensitive information will not be displayed in the frontend'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitTurnstile}>
|
||||
保存 Turnstile 设置
|
||||
Save Turnstile Settings
|
||||
</Form.Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
|
@ -30,15 +30,15 @@ function renderTimestamp(timestamp) {
|
||||
function renderStatus(status) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return <Label basic color='green'>已启用</Label>;
|
||||
return <Label basic color='green'>Enabled</Label>;
|
||||
case 2:
|
||||
return <Label basic color='red'> 已禁用 </Label>;
|
||||
return <Label basic color='red'> Disabled </Label>;
|
||||
case 3:
|
||||
return <Label basic color='yellow'> 已过期 </Label>;
|
||||
return <Label basic color='yellow'> Expired </Label>;
|
||||
case 4:
|
||||
return <Label basic color='grey'> 已耗尽 </Label>;
|
||||
return <Label basic color='grey'> Exhausted </Label>;
|
||||
default:
|
||||
return <Label basic color='black'> 未知状态 </Label>;
|
||||
return <Label basic color='black'> Unknown status </Label>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,9 +122,9 @@ const TokensTable = () => {
|
||||
url = `sk-${key}`;
|
||||
}
|
||||
if (await copy(url)) {
|
||||
showSuccess('已复制到剪贴板!');
|
||||
showSuccess('Copied to clipboard!');
|
||||
} else {
|
||||
showWarning('无法复制到剪贴板,请手动复制,已将令牌填入搜索框。');
|
||||
showWarning('Unable to copy to clipboard, please copy manually, the token has been entered into the search box。');
|
||||
setSearchKeyword(url);
|
||||
}
|
||||
};
|
||||
@ -195,7 +195,7 @@ const TokensTable = () => {
|
||||
}
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('操作成功完成!');
|
||||
showSuccess('Operation successfully completed!');
|
||||
let token = res.data.data;
|
||||
let newTokens = [...tokens];
|
||||
let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
|
||||
@ -266,7 +266,7 @@ const TokensTable = () => {
|
||||
icon='search'
|
||||
fluid
|
||||
iconPosition='left'
|
||||
placeholder='搜索令牌的名称 ...'
|
||||
placeholder='Search for the name of the token...'
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={handleKeywordChange}
|
||||
@ -282,7 +282,7 @@ const TokensTable = () => {
|
||||
sortToken('name');
|
||||
}}
|
||||
>
|
||||
名称
|
||||
Name
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -290,7 +290,7 @@ const TokensTable = () => {
|
||||
sortToken('status');
|
||||
}}
|
||||
>
|
||||
状态
|
||||
Status
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -298,7 +298,7 @@ const TokensTable = () => {
|
||||
sortToken('used_quota');
|
||||
}}
|
||||
>
|
||||
已用额度
|
||||
Used quota
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -306,7 +306,7 @@ const TokensTable = () => {
|
||||
sortToken('remain_quota');
|
||||
}}
|
||||
>
|
||||
剩余额度
|
||||
Remaining quota
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -314,7 +314,7 @@ const TokensTable = () => {
|
||||
sortToken('created_time');
|
||||
}}
|
||||
>
|
||||
创建时间
|
||||
Creation time
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -322,9 +322,9 @@ const TokensTable = () => {
|
||||
sortToken('expired_time');
|
||||
}}
|
||||
>
|
||||
过期时间
|
||||
Expiration time
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell>操作</Table.HeaderCell>
|
||||
<Table.HeaderCell>Operation</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
|
||||
@ -338,12 +338,12 @@ const TokensTable = () => {
|
||||
if (token.deleted) return <></>;
|
||||
return (
|
||||
<Table.Row key={token.id}>
|
||||
<Table.Cell>{token.name ? token.name : '无'}</Table.Cell>
|
||||
<Table.Cell>{token.name ? token.name : 'None'}</Table.Cell>
|
||||
<Table.Cell>{renderStatus(token.status)}</Table.Cell>
|
||||
<Table.Cell>{renderQuota(token.used_quota)}</Table.Cell>
|
||||
<Table.Cell>{token.unlimited_quota ? '无限制' : renderQuota(token.remain_quota, 2)}</Table.Cell>
|
||||
<Table.Cell>{token.unlimited_quota ? 'Unlimited' : renderQuota(token.remain_quota, 2)}</Table.Cell>
|
||||
<Table.Cell>{renderTimestamp(token.created_time)}</Table.Cell>
|
||||
<Table.Cell>{token.expired_time === -1 ? '永不过期' : renderTimestamp(token.expired_time)}</Table.Cell>
|
||||
<Table.Cell>{token.expired_time === -1 ? 'Never expires' : renderTimestamp(token.expired_time)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div>
|
||||
<Button.Group color='green' size={'small'}>
|
||||
@ -354,7 +354,7 @@ const TokensTable = () => {
|
||||
await onCopy('', token.key);
|
||||
}}
|
||||
>
|
||||
复制
|
||||
Copy
|
||||
</Button>
|
||||
<Dropdown
|
||||
className='button icon'
|
||||
@ -372,7 +372,7 @@ const TokensTable = () => {
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative>
|
||||
删除
|
||||
Delete
|
||||
</Button>
|
||||
}
|
||||
on='click'
|
||||
@ -385,7 +385,7 @@ const TokensTable = () => {
|
||||
manageToken(token.id, 'delete', idx);
|
||||
}}
|
||||
>
|
||||
删除令牌 {token.name}
|
||||
Delete Token {token.name}
|
||||
</Button>
|
||||
</Popup>
|
||||
<Button
|
||||
@ -398,14 +398,14 @@ const TokensTable = () => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{token.status === 1 ? '禁用' : '启用'}
|
||||
{token.status === 1 ? 'Disable' : 'Enable'}
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
as={Link}
|
||||
to={'/token/edit/' + token.id}
|
||||
>
|
||||
编辑
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
@ -418,16 +418,16 @@ const TokensTable = () => {
|
||||
<Table.Row>
|
||||
<Table.HeaderCell colSpan='7'>
|
||||
<Button size='small' as={Link} to='/token/add' loading={loading}>
|
||||
添加新的令牌
|
||||
Add New Token
|
||||
</Button>
|
||||
<Button size='small' onClick={refresh} loading={loading}>刷新</Button>
|
||||
<Button size='small' onClick={refresh} loading={loading}>Refresh</Button>
|
||||
<Dropdown
|
||||
placeholder='排序方式'
|
||||
selection
|
||||
options={[
|
||||
{ key: '', text: '默认排序', value: '' },
|
||||
{ key: 'remain_quota', text: '按剩余额度排序', value: 'remain_quota' },
|
||||
{ key: 'used_quota', text: '按已用额度排序', value: 'used_quota' },
|
||||
{ key: '', text: 'Default排序', value: '' },
|
||||
{ key: 'remain_quota', text: '按Remaining quota排序', value: 'remain_quota' },
|
||||
{ key: 'used_quota', text: '按Used quota排序', value: 'used_quota' },
|
||||
]}
|
||||
value={orderBy}
|
||||
onChange={handleOrderByChange}
|
||||
|
@ -9,13 +9,13 @@ import { renderGroup, renderNumber, renderQuota, renderText } from '../helpers/r
|
||||
function renderRole(role) {
|
||||
switch (role) {
|
||||
case 1:
|
||||
return <Label>普通用户</Label>;
|
||||
return <Label>Regular user</Label>;
|
||||
case 10:
|
||||
return <Label color='yellow'>管理员</Label>;
|
||||
return <Label color='yellow'>Administrator</Label>;
|
||||
case 100:
|
||||
return <Label color='orange'>超级管理员</Label>;
|
||||
return <Label color='orange'>Super administrator</Label>;
|
||||
default:
|
||||
return <Label color='red'>未知身份</Label>;
|
||||
return <Label color='red'>Unknown Identity</Label>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ const UsersTable = () => {
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('操作成功完成!');
|
||||
showSuccess('Operation successfully completed!');
|
||||
let user = res.data.data;
|
||||
let newUsers = [...users];
|
||||
let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
|
||||
@ -90,17 +90,17 @@ const UsersTable = () => {
|
||||
const renderStatus = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return <Label basic>已激活</Label>;
|
||||
return <Label basic>Activated</Label>;
|
||||
case 2:
|
||||
return (
|
||||
<Label basic color='red'>
|
||||
已封禁
|
||||
Banned
|
||||
</Label>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Label basic color='grey'>
|
||||
未知状态
|
||||
Unknown status
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
@ -162,7 +162,7 @@ const UsersTable = () => {
|
||||
icon='search'
|
||||
fluid
|
||||
iconPosition='left'
|
||||
placeholder='搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...'
|
||||
placeholder='Search user ID, username, display name, and email address...'
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={handleKeywordChange}
|
||||
@ -186,7 +186,7 @@ const UsersTable = () => {
|
||||
sortUser('username');
|
||||
}}
|
||||
>
|
||||
用户名
|
||||
Username
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -194,7 +194,7 @@ const UsersTable = () => {
|
||||
sortUser('group');
|
||||
}}
|
||||
>
|
||||
分组
|
||||
Group
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -202,7 +202,7 @@ const UsersTable = () => {
|
||||
sortUser('quota');
|
||||
}}
|
||||
>
|
||||
统计信息
|
||||
Statistics
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -210,7 +210,7 @@ const UsersTable = () => {
|
||||
sortUser('role');
|
||||
}}
|
||||
>
|
||||
用户角色
|
||||
User Role
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -218,9 +218,9 @@ const UsersTable = () => {
|
||||
sortUser('status');
|
||||
}}
|
||||
>
|
||||
状态
|
||||
Status
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell>操作</Table.HeaderCell>
|
||||
<Table.HeaderCell>Operation</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
|
||||
@ -237,7 +237,7 @@ const UsersTable = () => {
|
||||
<Table.Cell>{user.id}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Popup
|
||||
content={user.email ? user.email : '未绑定邮箱地址'}
|
||||
content={user.email ? user.email : 'Email not bound'}
|
||||
key={user.username}
|
||||
header={user.display_name ? user.display_name : user.username}
|
||||
trigger={<span>{renderText(user.username, 15)}</span>}
|
||||
@ -246,12 +246,12 @@ const UsersTable = () => {
|
||||
</Table.Cell>
|
||||
<Table.Cell>{renderGroup(user.group)}</Table.Cell>
|
||||
{/*<Table.Cell>*/}
|
||||
{/* {user.email ? <Popup hoverable content={user.email} trigger={<span>{renderText(user.email, 24)}</span>} /> : '无'}*/}
|
||||
{/* {user.email ? <Popup hoverable content={user.email} trigger={<span>{renderText(user.email, 24)}</span>} /> : 'None'}*/}
|
||||
{/*</Table.Cell>*/}
|
||||
<Table.Cell>
|
||||
<Popup content='剩余额度' trigger={<Label basic>{renderQuota(user.quota)}</Label>} />
|
||||
<Popup content='已用额度' trigger={<Label basic>{renderQuota(user.used_quota)}</Label>} />
|
||||
<Popup content='请求次数' trigger={<Label basic>{renderNumber(user.request_count)}</Label>} />
|
||||
<Popup content='Remaining quota' trigger={<Label basic>{renderQuota(user.quota)}</Label>} />
|
||||
<Popup content='Used quota' trigger={<Label basic>{renderQuota(user.used_quota)}</Label>} />
|
||||
<Popup content='Number of Requests' trigger={<Label basic>{renderNumber(user.request_count)}</Label>} />
|
||||
</Table.Cell>
|
||||
<Table.Cell>{renderRole(user.role)}</Table.Cell>
|
||||
<Table.Cell>{renderStatus(user.status)}</Table.Cell>
|
||||
@ -265,7 +265,7 @@ const UsersTable = () => {
|
||||
}}
|
||||
disabled={user.role === 100}
|
||||
>
|
||||
提升
|
||||
Promote
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
@ -275,12 +275,12 @@ const UsersTable = () => {
|
||||
}}
|
||||
disabled={user.role === 100}
|
||||
>
|
||||
降级
|
||||
Demote
|
||||
</Button>
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative disabled={user.role === 100}>
|
||||
删除
|
||||
Delete
|
||||
</Button>
|
||||
}
|
||||
on='click'
|
||||
@ -293,7 +293,7 @@ const UsersTable = () => {
|
||||
manageUser(user.username, 'delete', idx);
|
||||
}}
|
||||
>
|
||||
删除用户 {user.username}
|
||||
Delete User {user.username}
|
||||
</Button>
|
||||
</Popup>
|
||||
<Button
|
||||
@ -307,14 +307,14 @@ const UsersTable = () => {
|
||||
}}
|
||||
disabled={user.role === 100}
|
||||
>
|
||||
{user.status === 1 ? '禁用' : '启用'}
|
||||
{user.status === 1 ? 'Disable' : 'Enable'}
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
as={Link}
|
||||
to={'/user/edit/' + user.id}
|
||||
>
|
||||
编辑
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
@ -327,16 +327,16 @@ const UsersTable = () => {
|
||||
<Table.Row>
|
||||
<Table.HeaderCell colSpan='7'>
|
||||
<Button size='small' as={Link} to='/user/add' loading={loading}>
|
||||
添加新的用户
|
||||
Add New User
|
||||
</Button>
|
||||
<Dropdown
|
||||
placeholder='排序方式'
|
||||
selection
|
||||
options={[
|
||||
{ key: '', text: '默认排序', value: '' },
|
||||
{ key: 'quota', text: '按剩余额度排序', value: 'quota' },
|
||||
{ key: 'used_quota', text: '按已用额度排序', value: 'used_quota' },
|
||||
{ key: 'request_count', text: '按请求次数排序', value: 'request_count' },
|
||||
{ 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' },
|
||||
]}
|
||||
value={orderBy}
|
||||
onChange={handleOrderByChange}
|
||||
|
@ -15,7 +15,7 @@ export const CHANNEL_OPTIONS = [
|
||||
{ key: 19, text: '360 智脑', value: 19, color: 'blue' },
|
||||
{ key: 25, text: 'Moonshot AI', value: 25, color: 'black' },
|
||||
{ key: 23, text: '腾讯混元', value: 23, color: 'teal' },
|
||||
{ key: 26, text: '百川大模型', value: 26, color: 'orange' },
|
||||
{ key: 26, text: '百川大Model', value: 26, color: 'orange' },
|
||||
{ key: 27, text: 'MiniMax', value: 27, color: 'red' },
|
||||
{ key: 29, text: 'Groq', value: 29, color: 'orange' },
|
||||
{ key: 30, text: 'Ollama', value: 30, color: 'black' },
|
||||
@ -32,17 +32,17 @@ export const CHANNEL_OPTIONS = [
|
||||
{ key: 44, text: 'SiliconFlow', value: 44, color: 'blue' },
|
||||
{ key: 45, text: 'xAI', value: 45, color: 'blue' },
|
||||
{ key: 46, text: 'Replicate', value: 46, color: 'blue' },
|
||||
{ key: 8, text: '自定义渠道', value: 8, color: 'pink' },
|
||||
{ key: 8, text: 'CustomChannel', value: 8, color: 'pink' },
|
||||
{ key: 22, text: '知识库:FastGPT', value: 22, color: 'blue' },
|
||||
{ key: 21, text: '知识库:AI Proxy', value: 21, color: 'purple' },
|
||||
{ key: 20, text: '代理:OpenRouter', value: 20, color: 'black' },
|
||||
{ key: 2, text: '代理:API2D', value: 2, color: 'blue' },
|
||||
{ key: 5, text: '代理:OpenAI-SB', value: 5, color: 'brown' },
|
||||
{ key: 7, text: '代理:OhMyGPT', value: 7, color: 'purple' },
|
||||
{ key: 10, text: '代理:AI Proxy', value: 10, color: 'purple' },
|
||||
{ key: 4, text: '代理:CloseAI', value: 4, color: 'teal' },
|
||||
{ key: 6, text: '代理:OpenAI Max', value: 6, color: 'violet' },
|
||||
{ key: 9, text: '代理:AI.LS', value: 9, color: 'yellow' },
|
||||
{ key: 12, text: '代理:API2GPT', value: 12, color: 'blue' },
|
||||
{ key: 13, text: '代理:AIGC2D', value: 13, color: 'purple' }
|
||||
{ key: 20, text: 'Proxy:OpenRouter', value: 20, color: 'black' },
|
||||
{ key: 2, text: 'Proxy:API2D', value: 2, color: 'blue' },
|
||||
{ key: 5, text: 'Proxy:OpenAI-SB', value: 5, color: 'brown' },
|
||||
{ key: 7, text: 'Proxy:OhMyGPT', value: 7, color: 'purple' },
|
||||
{ key: 10, text: 'Proxy:AI Proxy', value: 10, color: 'purple' },
|
||||
{ key: 4, text: 'Proxy:CloseAI', value: 4, color: 'teal' },
|
||||
{ key: 6, text: 'Proxy:OpenAI Max', value: 6, color: 'violet' },
|
||||
{ key: 9, text: 'Proxy:AI.LS', value: 9, color: 'yellow' },
|
||||
{ key: 12, text: 'Proxy:API2GPT', value: 12, color: 'blue' },
|
||||
{ key: 13, text: 'Proxy:AIGC2D', value: 13, color: 'purple' }
|
||||
];
|
||||
|
@ -52,7 +52,7 @@ export function renderQuotaWithPrompt(quota, digits) {
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
displayInCurrency = displayInCurrency === 'true';
|
||||
if (displayInCurrency) {
|
||||
return `(等价金额:${renderQuota(quota, digits)})`;
|
||||
return `(Equivalent Amount:${renderQuota(quota, digits)})`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
@ -79,26 +79,26 @@ export function showError(error) {
|
||||
if (error.name === 'AxiosError') {
|
||||
switch (error.response.status) {
|
||||
case 401:
|
||||
// toast.error('错误:未登录或登录已过期,请重新登录!', showErrorOptions);
|
||||
// toast.error('Error: Not logged in or login has expired, please log in again!', showErrorOptions);
|
||||
window.location.href = '/login?expired=true';
|
||||
break;
|
||||
case 429:
|
||||
toast.error('错误:请求次数过多,请稍后再试!', showErrorOptions);
|
||||
toast.error('Error: Too many requests, please try again later!', showErrorOptions);
|
||||
break;
|
||||
case 500:
|
||||
toast.error('错误:服务器内部错误,请联系管理员!', showErrorOptions);
|
||||
toast.error('Error: Server internal error, please contact the online customer service!', showErrorOptions);
|
||||
break;
|
||||
case 405:
|
||||
toast.info('本站仅作演示之用,无服务端!');
|
||||
toast.info('This site is for demonstration purposes only, no server!');
|
||||
break;
|
||||
default:
|
||||
toast.error('错误:' + error.message, showErrorOptions);
|
||||
toast.error('Error:' + error.message, showErrorOptions);
|
||||
}
|
||||
return;
|
||||
}
|
||||
toast.error('错误:' + error.message, showErrorOptions);
|
||||
toast.error('Error:' + error.message, showErrorOptions);
|
||||
} else {
|
||||
toast.error('错误:' + error, showErrorOptions);
|
||||
toast.error('Error:' + error, showErrorOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ const About = () => {
|
||||
localStorage.setItem('about', aboutContent);
|
||||
} else {
|
||||
showError(message);
|
||||
setAbout('加载关于内容失败...');
|
||||
setAbout('Failed to load the About content...');
|
||||
}
|
||||
setAboutLoaded(true);
|
||||
};
|
||||
@ -34,9 +34,9 @@ const About = () => {
|
||||
{
|
||||
aboutLoaded && about === '' ? <>
|
||||
<Segment>
|
||||
<Header as='h3'>关于</Header>
|
||||
<p>可在设置页面设置关于内容,支持 HTML & Markdown</p>
|
||||
项目仓库地址:
|
||||
<Header as='h3'>About</Header>
|
||||
<p>You can set the content about in the settings page, support HTML & Markdown</p>
|
||||
Project Repository Address:
|
||||
<a href='https://github.com/songquanpeng/one-api'>
|
||||
https://github.com/songquanpeng/one-api
|
||||
</a>
|
||||
|
@ -11,18 +11,18 @@ const MODEL_MAPPING_EXAMPLE = {
|
||||
};
|
||||
|
||||
function type2secretPrompt(type) {
|
||||
// inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')
|
||||
// inputs.type === 15 ? 'Enter in the following format:APIKey|SecretKey' : (inputs.type === 18 ? 'Enter in the following format:APPID|APISecret|APIKey' : 'Please enter the authentication key corresponding to the channel')
|
||||
switch (type) {
|
||||
case 15:
|
||||
return '按照如下格式输入:APIKey|SecretKey';
|
||||
return 'Enter in the following format:APIKey|SecretKey';
|
||||
case 18:
|
||||
return '按照如下格式输入:APPID|APISecret|APIKey';
|
||||
return 'Enter in the following format:APPID|APISecret|APIKey';
|
||||
case 22:
|
||||
return '按照如下格式输入:APIKey-AppId,例如:fastgpt-0sp2gtvfdgyi4k30jwlgwf1i-64f335d84283f05518e9e041';
|
||||
return 'Enter in the following format:APIKey-AppId,For example:fastgpt-0sp2gtvfdgyi4k30jwlgwf1i-64f335d84283f05518e9e041';
|
||||
case 23:
|
||||
return '按照如下格式输入:AppId|SecretId|SecretKey';
|
||||
return 'Enter in the following format:AppId|SecretId|SecretKey';
|
||||
default:
|
||||
return '请输入渠道对应的鉴权密钥';
|
||||
return 'Please enter the authentication key corresponding to the channel';
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,15 +168,15 @@ const EditChannel = () => {
|
||||
}
|
||||
}
|
||||
if (!isEdit && (inputs.name === '' || inputs.key === '')) {
|
||||
showInfo('请填写渠道名称和渠道密钥!');
|
||||
showInfo('Please fill in the ChannelName and ChannelKey!');
|
||||
return;
|
||||
}
|
||||
if (inputs.type !== 43 && inputs.models.length === 0) {
|
||||
showInfo('请至少选择一个模型!');
|
||||
showInfo('Please select at least one Model!');
|
||||
return;
|
||||
}
|
||||
if (inputs.model_mapping !== '' && !verifyJSON(inputs.model_mapping)) {
|
||||
showInfo('模型映射必须是合法的 JSON 格式!');
|
||||
showInfo('Model mapping must be in valid JSON format!');
|
||||
return;
|
||||
}
|
||||
let localInputs = {...inputs};
|
||||
@ -198,9 +198,9 @@ const EditChannel = () => {
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
if (isEdit) {
|
||||
showSuccess('渠道更新成功!');
|
||||
showSuccess('Channel updated successfully!');
|
||||
} else {
|
||||
showSuccess('渠道创建成功!');
|
||||
showSuccess('Channel created successfully!');
|
||||
setInputs(originInputs);
|
||||
}
|
||||
} else {
|
||||
@ -229,11 +229,11 @@ const EditChannel = () => {
|
||||
return (
|
||||
<>
|
||||
<Segment loading={loading}>
|
||||
<Header as='h3'>{isEdit ? '更新渠道信息' : '创建新的渠道'}</Header>
|
||||
<Header as='h3'>{isEdit ? 'Update Channel Information' : 'Create New Channel'}</Header>
|
||||
<Form autoComplete='new-password'>
|
||||
<Form.Field>
|
||||
<Form.Select
|
||||
label='类型'
|
||||
label='Type'
|
||||
name='type'
|
||||
required
|
||||
search
|
||||
@ -246,15 +246,15 @@ const EditChannel = () => {
|
||||
inputs.type === 3 && (
|
||||
<>
|
||||
<Message>
|
||||
注意,<strong>模型部署名称必须和模型名称保持一致</strong>,因为 One API 会把请求体中的 model
|
||||
参数替换为你的部署名称(模型名称中的点会被剔除),<a target='_blank'
|
||||
href='https://github.com/songquanpeng/one-api/issues/133?notification_referrer_id=NT_kwDOAmJSYrM2NjIwMzI3NDgyOjM5OTk4MDUw#issuecomment-1571602271'>图片演示</a>。
|
||||
Note that, <strong>The model deployment name must be consistent with the model name</strong>, because One API will take the model in the request body
|
||||
Replace the parameter with your deployment name (dots in the model name will be removed),<a target='_blank'
|
||||
href='https://github.com/songquanpeng/one-api/issues/133?notification_referrer_id=NT_kwDOAmJSYrM2NjIwMzI3NDgyOjM5OTk4MDUw#issuecomment-1571602271'>Image demo</a>。
|
||||
</Message>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='AZURE_OPENAI_ENDPOINT'
|
||||
name='base_url'
|
||||
placeholder={'请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com'}
|
||||
placeholder={'Please enter AZURE_OPENAI_ENDPOINT,For example:https://docs-test-001.openai.azure.com'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.base_url}
|
||||
autoComplete='new-password'
|
||||
@ -262,9 +262,9 @@ const EditChannel = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='默认 API 版本'
|
||||
label='Default API Version'
|
||||
name='other'
|
||||
placeholder={'请输入默认 API 版本,例如:2024-03-01-preview,该配置可以被实际的请求查询参数所覆盖'}
|
||||
placeholder={'请EnterDefault API Version,For example:2024-03-01-preview,该配置可以被实际的请求Query参数所覆盖'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.other}
|
||||
autoComplete='new-password'
|
||||
@ -279,7 +279,7 @@ const EditChannel = () => {
|
||||
<Form.Input
|
||||
label='Base URL'
|
||||
name='base_url'
|
||||
placeholder={'请输入自定义渠道的 Base URL,例如:https://openai.justsong.cn'}
|
||||
placeholder={'Please enter the Base URL of the custom channel,For example:https://openai.justsong.cn'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.base_url}
|
||||
autoComplete='new-password'
|
||||
@ -289,10 +289,10 @@ const EditChannel = () => {
|
||||
}
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='名称'
|
||||
label='Name'
|
||||
required
|
||||
name='name'
|
||||
placeholder={'请为渠道命名'}
|
||||
placeholder={'Please name the channel'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.name}
|
||||
autoComplete='new-password'
|
||||
@ -300,15 +300,15 @@ const EditChannel = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Dropdown
|
||||
label='分组'
|
||||
placeholder={'请选择可以使用该渠道的分组'}
|
||||
label='Group'
|
||||
placeholder={'请选择可以使用该Channel的Group'}
|
||||
name='groups'
|
||||
required
|
||||
fluid
|
||||
multiple
|
||||
selection
|
||||
allowAdditions
|
||||
additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
|
||||
additionLabel={'Please edit the group rate on the system settings page to add a new group:'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.groups}
|
||||
autoComplete='new-password'
|
||||
@ -319,9 +319,9 @@ const EditChannel = () => {
|
||||
inputs.type === 18 && (
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='模型版本'
|
||||
label='Model version'
|
||||
name='other'
|
||||
placeholder={'请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1'}
|
||||
placeholder={'Please enter the version of the Starfire model, note that it is the version number in the interface address, for example: v2.1'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.other}
|
||||
autoComplete='new-password'
|
||||
@ -335,7 +335,7 @@ const EditChannel = () => {
|
||||
<Form.Input
|
||||
label='知识库 ID'
|
||||
name='other'
|
||||
placeholder={'请输入知识库 ID,例如:123456'}
|
||||
placeholder={'请Enter知识库 ID,For example:123456'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.other}
|
||||
autoComplete='new-password'
|
||||
@ -349,7 +349,7 @@ const EditChannel = () => {
|
||||
<Form.Input
|
||||
label='插件参数'
|
||||
name='other'
|
||||
placeholder={'请输入插件参数,即 X-DashScope-Plugin 请求头的取值'}
|
||||
placeholder={'请Enter插件参数,即 X-DashScope-Plugin 请求头的取值'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.other}
|
||||
autoComplete='new-password'
|
||||
@ -360,14 +360,14 @@ const EditChannel = () => {
|
||||
{
|
||||
inputs.type === 34 && (
|
||||
<Message>
|
||||
对于 Coze 而言,模型名称即 Bot ID,你可以添加一个前缀 `bot-`,例如:`bot-123456`。
|
||||
对于 Coze 而言,Model name即 Bot ID,你可以添加一个前缀 `bot-`,For example:`bot-123456`。
|
||||
</Message>
|
||||
)
|
||||
}
|
||||
{
|
||||
inputs.type === 40 && (
|
||||
<Message>
|
||||
对于豆包而言,需要手动去 <a target="_blank" href="https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint">模型推理页面</a> 创建推理接入点,以接入点名称作为模型名称,例如:`ep-20240608051426-tkxvl`。
|
||||
对于豆包而言,需要手动去 <a target="_blank" href="https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint">Model推理页面</a> 创建推理接入点,以接入点Name作为Model name,For example:`ep-20240608051426-tkxvl`。
|
||||
</Message>
|
||||
)
|
||||
}
|
||||
@ -375,8 +375,8 @@ const EditChannel = () => {
|
||||
inputs.type !== 43 && (
|
||||
<Form.Field>
|
||||
<Form.Dropdown
|
||||
label='模型'
|
||||
placeholder={'请选择该渠道所支持的模型'}
|
||||
label='Model'
|
||||
placeholder={'Please select the model supported by the channel'}
|
||||
name='models'
|
||||
required
|
||||
fluid
|
||||
@ -399,18 +399,18 @@ const EditChannel = () => {
|
||||
<div style={{ lineHeight: '40px', marginBottom: '12px' }}>
|
||||
<Button type={'button'} onClick={() => {
|
||||
handleInputChange(null, { name: 'models', value: basicModels });
|
||||
}}>填入相关模型</Button>
|
||||
}}>填入相关Model</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
handleInputChange(null, { name: 'models', value: fullModels });
|
||||
}}>填入所有模型</Button>
|
||||
}}>Fill in all models</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
handleInputChange(null, { name: 'models', value: [] });
|
||||
}}>清除所有模型</Button>
|
||||
}}>Clear all models</Button>
|
||||
<Input
|
||||
action={
|
||||
<Button type={'button'} onClick={addCustomModel}>填入</Button>
|
||||
}
|
||||
placeholder='输入自定义模型名称'
|
||||
placeholder='EnterCustomModel name'
|
||||
value={customModel}
|
||||
onChange={(e, { value }) => {
|
||||
setCustomModel(value);
|
||||
@ -429,8 +429,8 @@ const EditChannel = () => {
|
||||
inputs.type !== 43 && (<>
|
||||
<Form.Field>
|
||||
<Form.TextArea
|
||||
label='模型重定向'
|
||||
placeholder={`此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`}
|
||||
label='Model redirection'
|
||||
placeholder={`This is optional, used to modify the model name in the request body, it's a JSON string, the key is the model name in the request, and the value is the model name to be replaced, for example:\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`}
|
||||
name='model_mapping'
|
||||
onChange={handleInputChange}
|
||||
value={inputs.model_mapping}
|
||||
@ -440,8 +440,8 @@ const EditChannel = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.TextArea
|
||||
label='系统提示词'
|
||||
placeholder={`此项可选,用于强制设置给定的系统提示词,请配合自定义模型 & 模型重定向使用,首先创建一个唯一的自定义模型名称并在上面填入,之后将该自定义模型重定向映射到该渠道一个原生支持的模型`}
|
||||
label='SystemPrompt词'
|
||||
placeholder={`此项可选,用于强制Settings给定的SystemPrompt词,请配合CustomModel & Model redirection使用,首先创建一个唯一的CustomModel name并在上面填入,之后将该CustomModel redirection映射到该Channel一个原生支持的Model`}
|
||||
name='system_prompt'
|
||||
onChange={handleInputChange}
|
||||
value={inputs.system_prompt}
|
||||
@ -524,7 +524,7 @@ const EditChannel = () => {
|
||||
label='User ID'
|
||||
name='user_id'
|
||||
required
|
||||
placeholder={'生成该密钥的用户 ID'}
|
||||
placeholder={'生成该Key的Users ID'}
|
||||
onChange={handleConfigChange}
|
||||
value={config.user_id}
|
||||
autoComplete=''
|
||||
@ -533,10 +533,10 @@ const EditChannel = () => {
|
||||
{
|
||||
inputs.type !== 33 && inputs.type !== 42 && (batch ? <Form.Field>
|
||||
<Form.TextArea
|
||||
label='密钥'
|
||||
label='Key'
|
||||
name='key'
|
||||
required
|
||||
placeholder={'请输入密钥,一行一个'}
|
||||
placeholder={'Please enter the key, one per line'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.key}
|
||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
@ -544,7 +544,7 @@ const EditChannel = () => {
|
||||
/>
|
||||
</Form.Field> : <Form.Field>
|
||||
<Form.Input
|
||||
label='密钥'
|
||||
label='Key'
|
||||
name='key'
|
||||
required
|
||||
placeholder={type2secretPrompt(inputs.type)}
|
||||
@ -561,7 +561,7 @@ const EditChannel = () => {
|
||||
label='Account ID'
|
||||
name='user_id'
|
||||
required
|
||||
placeholder={'请输入 Account ID,例如:d8d7c61dbc334c32d3ced580e4bf42b4'}
|
||||
placeholder={'请Enter Account ID,For example:d8d7c61dbc334c32d3ced580e4bf42b4'}
|
||||
onChange={handleConfigChange}
|
||||
value={config.user_id}
|
||||
autoComplete=''
|
||||
@ -573,7 +573,7 @@ const EditChannel = () => {
|
||||
inputs.type !== 33 && !isEdit && (
|
||||
<Form.Checkbox
|
||||
checked={batch}
|
||||
label='批量创建'
|
||||
label='Batch Create'
|
||||
name='batch'
|
||||
onChange={() => setBatch(!batch)}
|
||||
/>
|
||||
@ -583,9 +583,9 @@ const EditChannel = () => {
|
||||
inputs.type !== 3 && inputs.type !== 33 && inputs.type !== 8 && inputs.type !== 22 && (
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='代理'
|
||||
label='Proxy'
|
||||
name='base_url'
|
||||
placeholder={'此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com'}
|
||||
placeholder={'This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.base_url}
|
||||
autoComplete='new-password'
|
||||
@ -599,7 +599,7 @@ const EditChannel = () => {
|
||||
<Form.Input
|
||||
label='私有部署地址'
|
||||
name='base_url'
|
||||
placeholder={'请输入私有部署地址,格式为:https://fastgpt.run/api/openapi'}
|
||||
placeholder={'请Enter私有部署地址,格式为:https://fastgpt.run/api/openapi'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.base_url}
|
||||
autoComplete='new-password'
|
||||
@ -607,8 +607,8 @@ const EditChannel = () => {
|
||||
</Form.Field>
|
||||
)
|
||||
}
|
||||
<Button onClick={handleCancel}>取消</Button>
|
||||
<Button type={isEdit ? 'button' : 'submit'} positive onClick={submit}>提交</Button>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
<Button type={isEdit ? 'button' : 'submit'} positive onClick={submit}>Submit</Button>
|
||||
</Form>
|
||||
</Segment>
|
||||
</>
|
||||
|
@ -5,7 +5,7 @@ import ChannelsTable from '../../components/ChannelsTable';
|
||||
const Channel = () => (
|
||||
<>
|
||||
<Segment>
|
||||
<Header as='h3'>管理渠道</Header>
|
||||
<Header as='h3'>Manage Channels</Header>
|
||||
<ChannelsTable />
|
||||
</Segment>
|
||||
</>
|
||||
|
@ -37,7 +37,7 @@ const Home = () => {
|
||||
localStorage.setItem('home_page_content', content);
|
||||
} else {
|
||||
showError(message);
|
||||
setHomePageContent('加载首页内容失败...');
|
||||
setHomePageContent('Failed to load homepage content...');
|
||||
}
|
||||
setHomePageContentLoaded(true);
|
||||
};
|
||||
@ -56,18 +56,18 @@ const Home = () => {
|
||||
{
|
||||
homePageContentLoaded && homePageContent === '' ? <>
|
||||
<Segment>
|
||||
<Header as='h3'>系统状况</Header>
|
||||
<Header as='h3'>System status</Header>
|
||||
<Grid columns={2} stackable>
|
||||
<Grid.Column>
|
||||
<Card fluid>
|
||||
<Card.Content>
|
||||
<Card.Header>系统信息</Card.Header>
|
||||
<Card.Meta>系统信息总览</Card.Meta>
|
||||
<Card.Header>System information</Card.Header>
|
||||
<Card.Meta>System information overview</Card.Meta>
|
||||
<Card.Description>
|
||||
<p>名称:{statusState?.status?.system_name}</p>
|
||||
<p>版本:{statusState?.status?.version ? statusState?.status?.version : "unknown"}</p>
|
||||
<p>Name:{statusState?.status?.system_name}</p>
|
||||
<p>Version:{statusState?.status?.version ? statusState?.status?.version : "unknown"}</p>
|
||||
<p>
|
||||
源码:
|
||||
Source code:
|
||||
<a
|
||||
href='https://github.com/songquanpeng/one-api'
|
||||
target='_blank'
|
||||
@ -75,7 +75,7 @@ const Home = () => {
|
||||
https://github.com/songquanpeng/one-api
|
||||
</a>
|
||||
</p>
|
||||
<p>启动时间:{getStartTimeString()}</p>
|
||||
<p>Startup time:{getStartTimeString()}</p>
|
||||
</Card.Description>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
@ -83,32 +83,32 @@ const Home = () => {
|
||||
<Grid.Column>
|
||||
<Card fluid>
|
||||
<Card.Content>
|
||||
<Card.Header>系统配置</Card.Header>
|
||||
<Card.Meta>系统配置总览</Card.Meta>
|
||||
<Card.Header>System configuration</Card.Header>
|
||||
<Card.Meta>System configuration overview</Card.Meta>
|
||||
<Card.Description>
|
||||
<p>
|
||||
邮箱验证:
|
||||
Email verification:
|
||||
{statusState?.status?.email_verification === true
|
||||
? '已启用'
|
||||
: '未启用'}
|
||||
? 'Enabled'
|
||||
: 'Not enabled'}
|
||||
</p>
|
||||
<p>
|
||||
GitHub 身份验证:
|
||||
GitHub Authentication:
|
||||
{statusState?.status?.github_oauth === true
|
||||
? '已启用'
|
||||
: '未启用'}
|
||||
? 'Enabled'
|
||||
: 'Not enabled'}
|
||||
</p>
|
||||
<p>
|
||||
微信身份验证:
|
||||
WeChat Authentication:
|
||||
{statusState?.status?.wechat_login === true
|
||||
? '已启用'
|
||||
: '未启用'}
|
||||
? 'Enabled'
|
||||
: 'Not enabled'}
|
||||
</p>
|
||||
<p>
|
||||
Turnstile 用户校验:
|
||||
Turnstile user verification:
|
||||
{statusState?.status?.turnstile_check === true
|
||||
? '已启用'
|
||||
: '未启用'}
|
||||
? 'Enabled'
|
||||
: 'Not enabled'}
|
||||
</p>
|
||||
</Card.Description>
|
||||
</Card.Content>
|
||||
|
@ -4,8 +4,8 @@ import { Message } from 'semantic-ui-react';
|
||||
const NotFound = () => (
|
||||
<>
|
||||
<Message negative>
|
||||
<Message.Header>页面不存在</Message.Header>
|
||||
<p>请检查你的浏览器地址是否正确</p>
|
||||
<Message.Header>Page does not exist</Message.Header>
|
||||
<p>Please check if your browser address is correct</p>
|
||||
</Message>
|
||||
</>
|
||||
);
|
||||
|
@ -58,9 +58,9 @@ const EditRedemption = () => {
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (isEdit) {
|
||||
showSuccess('兑换码更新成功!');
|
||||
showSuccess('Redemption code updated successfully!');
|
||||
} else {
|
||||
showSuccess('兑换码创建成功!');
|
||||
showSuccess('Redemption code created successfully!');
|
||||
setInputs(originInputs);
|
||||
}
|
||||
} else {
|
||||
@ -78,13 +78,13 @@ const EditRedemption = () => {
|
||||
return (
|
||||
<>
|
||||
<Segment loading={loading}>
|
||||
<Header as='h3'>{isEdit ? '更新兑换码信息' : '创建新的兑换码'}</Header>
|
||||
<Header as='h3'>{isEdit ? 'Update redemption code information' : 'Create a new redemption code'}</Header>
|
||||
<Form autoComplete='new-password'>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='名称'
|
||||
label='Name'
|
||||
name='name'
|
||||
placeholder={'请输入名称'}
|
||||
placeholder={'Please enter a name'}
|
||||
onChange={handleInputChange}
|
||||
value={name}
|
||||
autoComplete='new-password'
|
||||
@ -93,9 +93,9 @@ const EditRedemption = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label={`额度${renderQuotaWithPrompt(quota)}`}
|
||||
label={`Quota${renderQuotaWithPrompt(quota)}`}
|
||||
name='quota'
|
||||
placeholder={'请输入单个兑换码中包含的额度'}
|
||||
placeholder={'Please enter the quota included in a single redemption code'}
|
||||
onChange={handleInputChange}
|
||||
value={quota}
|
||||
autoComplete='new-password'
|
||||
@ -106,9 +106,9 @@ const EditRedemption = () => {
|
||||
!isEdit && <>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='生成数量'
|
||||
label='Generate quantity'
|
||||
name='count'
|
||||
placeholder={'请输入生成数量'}
|
||||
placeholder={'Please enter the quantity to generate'}
|
||||
onChange={handleInputChange}
|
||||
value={count}
|
||||
autoComplete='new-password'
|
||||
@ -117,8 +117,8 @@ const EditRedemption = () => {
|
||||
</Form.Field>
|
||||
</>
|
||||
}
|
||||
<Button positive onClick={submit}>提交</Button>
|
||||
<Button onClick={handleCancel}>取消</Button>
|
||||
<Button positive onClick={submit}>Submit</Button>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
</Form>
|
||||
</Segment>
|
||||
</>
|
||||
|
@ -5,7 +5,7 @@ import RedemptionsTable from '../../components/RedemptionsTable';
|
||||
const Redemption = () => (
|
||||
<>
|
||||
<Segment>
|
||||
<Header as='h3'>管理兑换码</Header>
|
||||
<Header as='h3'>Manage Redeem Codes</Header>
|
||||
<RedemptionsTable/>
|
||||
</Segment>
|
||||
</>
|
||||
|
@ -9,7 +9,7 @@ import OperationSetting from '../../components/OperationSetting';
|
||||
const Setting = () => {
|
||||
let panes = [
|
||||
{
|
||||
menuItem: '个人设置',
|
||||
menuItem: 'Personal settings',
|
||||
render: () => (
|
||||
<Tab.Pane attached={false}>
|
||||
<PersonalSetting />
|
||||
@ -20,7 +20,7 @@ const Setting = () => {
|
||||
|
||||
if (isRoot()) {
|
||||
panes.push({
|
||||
menuItem: '运营设置',
|
||||
menuItem: 'Operations settings',
|
||||
render: () => (
|
||||
<Tab.Pane attached={false}>
|
||||
<OperationSetting />
|
||||
@ -28,7 +28,7 @@ const Setting = () => {
|
||||
)
|
||||
});
|
||||
panes.push({
|
||||
menuItem: '系统设置',
|
||||
menuItem: 'System settings',
|
||||
render: () => (
|
||||
<Tab.Pane attached={false}>
|
||||
<SystemSetting />
|
||||
@ -36,7 +36,7 @@ const Setting = () => {
|
||||
)
|
||||
});
|
||||
panes.push({
|
||||
menuItem: '其他设置',
|
||||
menuItem: 'Other settings',
|
||||
render: () => (
|
||||
<Tab.Pane attached={false}>
|
||||
<OtherSetting />
|
||||
|
@ -95,7 +95,7 @@ const EditToken = () => {
|
||||
if (localInputs.expired_time !== -1) {
|
||||
let time = Date.parse(localInputs.expired_time);
|
||||
if (isNaN(time)) {
|
||||
showError('过期时间格式错误!');
|
||||
showError('Expiration time format error!');
|
||||
return;
|
||||
}
|
||||
localInputs.expired_time = Math.ceil(time / 1000);
|
||||
@ -110,9 +110,9 @@ const EditToken = () => {
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
if (isEdit) {
|
||||
showSuccess('令牌更新成功!');
|
||||
showSuccess('Token updated successfully!');
|
||||
} else {
|
||||
showSuccess('令牌创建成功,请在列表页面点击复制获取令牌!');
|
||||
showSuccess('Token created successfully, please click copy on the list page to get the token!');
|
||||
setInputs(originInputs);
|
||||
}
|
||||
} else {
|
||||
@ -123,13 +123,13 @@ const EditToken = () => {
|
||||
return (
|
||||
<>
|
||||
<Segment loading={loading}>
|
||||
<Header as='h3'>{isEdit ? '更新令牌信息' : '创建新的令牌'}</Header>
|
||||
<Header as='h3'>{isEdit ? 'Update key information' : 'Create a new key'}</Header>
|
||||
<Form autoComplete='new-password'>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='名称'
|
||||
label='Name'
|
||||
name='name'
|
||||
placeholder={'请输入名称'}
|
||||
placeholder={'Please enter a name'}
|
||||
onChange={handleInputChange}
|
||||
value={name}
|
||||
autoComplete='new-password'
|
||||
@ -138,8 +138,8 @@ const EditToken = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Dropdown
|
||||
label='模型范围'
|
||||
placeholder={'请选择允许使用的模型,留空则不进行限制'}
|
||||
label='Model范围'
|
||||
placeholder={'请选择允许使用的Model,留空则不进行限制'}
|
||||
name='models'
|
||||
fluid
|
||||
multiple
|
||||
@ -158,7 +158,7 @@ const EditToken = () => {
|
||||
<Form.Input
|
||||
label='IP 限制'
|
||||
name='subnet'
|
||||
placeholder={'请输入允许访问的网段,例如:192.168.0.0/24,请使用英文逗号分隔多个网段'}
|
||||
placeholder={'请Enter允许访问的网段,For example:192.168.0.0/24,请使用英文逗号分隔多个网段'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.subnet}
|
||||
autoComplete='new-password'
|
||||
@ -166,9 +166,9 @@ const EditToken = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='过期时间'
|
||||
label='Expiration time'
|
||||
name='expired_time'
|
||||
placeholder={'请输入过期时间,格式为 yyyy-MM-dd HH:mm:ss,-1 表示无限制'}
|
||||
placeholder={'Please enter the expiration time, the format is yyyy-MM-dd HH:mm:ss, -1 means unlimited'}
|
||||
onChange={handleInputChange}
|
||||
value={expired_time}
|
||||
autoComplete='new-password'
|
||||
@ -178,26 +178,26 @@ const EditToken = () => {
|
||||
<div style={{ lineHeight: '40px' }}>
|
||||
<Button type={'button'} onClick={() => {
|
||||
setExpiredTime(0, 0, 0, 0);
|
||||
}}>永不过期</Button>
|
||||
}}>Never expires</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
setExpiredTime(1, 0, 0, 0);
|
||||
}}>一个月后过期</Button>
|
||||
}}>Expires after one month</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
setExpiredTime(0, 1, 0, 0);
|
||||
}}>一天后过期</Button>
|
||||
}}>Expires after one day</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
setExpiredTime(0, 0, 1, 0);
|
||||
}}>一小时后过期</Button>
|
||||
}}>Expires after one hour</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
setExpiredTime(0, 0, 0, 1);
|
||||
}}>一分钟后过期</Button>
|
||||
}}>Expires after one minute</Button>
|
||||
</div>
|
||||
<Message>注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。</Message>
|
||||
<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>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label={`额度${renderQuotaWithPrompt(remain_quota)}`}
|
||||
label={`Quota${renderQuotaWithPrompt(remain_quota)}`}
|
||||
name='remain_quota'
|
||||
placeholder={'请输入额度'}
|
||||
placeholder={'Please enter the quota'}
|
||||
onChange={handleInputChange}
|
||||
value={remain_quota}
|
||||
autoComplete='new-password'
|
||||
@ -207,9 +207,9 @@ const EditToken = () => {
|
||||
</Form.Field>
|
||||
<Button type={'button'} onClick={() => {
|
||||
setUnlimitedQuota();
|
||||
}}>{unlimited_quota ? '取消无限额度' : '设为无限额度'}</Button>
|
||||
<Button floated='right' positive onClick={submit}>提交</Button>
|
||||
<Button floated='right' onClick={handleCancel}>取消</Button>
|
||||
}}>{unlimited_quota ? 'Cancel unlimited quota' : 'Set to unlimited quota'}</Button>
|
||||
<Button floated='right' positive onClick={submit}>Submit</Button>
|
||||
<Button floated='right' onClick={handleCancel}>Cancel</Button>
|
||||
</Form>
|
||||
</Segment>
|
||||
</>
|
||||
|
@ -5,7 +5,7 @@ import TokensTable from '../../components/TokensTable';
|
||||
const Token = () => (
|
||||
<>
|
||||
<Segment>
|
||||
<Header as='h3'>我的令牌</Header>
|
||||
<Header as='h3'>My keys</Header>
|
||||
<TokensTable/>
|
||||
</Segment>
|
||||
</>
|
||||
|
@ -12,7 +12,7 @@ const TopUp = () => {
|
||||
|
||||
const topUp = async () => {
|
||||
if (redemptionCode === '') {
|
||||
showInfo('请输入充值码!')
|
||||
showInfo('Please enter the recharge code!')
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
@ -22,7 +22,7 @@ const TopUp = () => {
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
showSuccess('充值成功!');
|
||||
showSuccess('Recharge successful!');
|
||||
setUserQuota((quota) => {
|
||||
return quota + data;
|
||||
});
|
||||
@ -31,7 +31,7 @@ const TopUp = () => {
|
||||
showError(message);
|
||||
}
|
||||
} catch (err) {
|
||||
showError('请求失败');
|
||||
showError('Request failed');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@ -39,7 +39,7 @@ const TopUp = () => {
|
||||
|
||||
const openTopUpLink = () => {
|
||||
if (!topUpLink) {
|
||||
showError('超级管理员未设置充值链接!');
|
||||
showError('The super administrator did not set a recharge link!');
|
||||
return;
|
||||
}
|
||||
let url = new URL(topUpLink);
|
||||
@ -76,12 +76,12 @@ const TopUp = () => {
|
||||
|
||||
return (
|
||||
<Segment>
|
||||
<Header as='h3'>充值额度</Header>
|
||||
<Header as='h3'>Recharge quota</Header>
|
||||
<Grid columns={2} stackable>
|
||||
<Grid.Column>
|
||||
<Form>
|
||||
<Form.Input
|
||||
placeholder='兑换码'
|
||||
placeholder='Redeem Code'
|
||||
name='redemptionCode'
|
||||
value={redemptionCode}
|
||||
onChange={(e) => {
|
||||
@ -89,10 +89,10 @@ const TopUp = () => {
|
||||
}}
|
||||
/>
|
||||
<Button color='green' onClick={openTopUpLink}>
|
||||
充值
|
||||
Recharge
|
||||
</Button>
|
||||
<Button color='yellow' onClick={topUp} disabled={isSubmitting}>
|
||||
{isSubmitting ? '兑换中...' : '兑换'}
|
||||
{isSubmitting ? 'Redeeming...' : 'Redeem'}
|
||||
</Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
@ -100,7 +100,7 @@ const TopUp = () => {
|
||||
<Statistic.Group widths='one'>
|
||||
<Statistic>
|
||||
<Statistic.Value>{renderQuota(userQuota)}</Statistic.Value>
|
||||
<Statistic.Label>剩余额度</Statistic.Label>
|
||||
<Statistic.Label>Remaining quota</Statistic.Label>
|
||||
</Statistic>
|
||||
</Statistic.Group>
|
||||
</Grid.Column>
|
||||
|
@ -20,7 +20,7 @@ const AddUser = () => {
|
||||
const res = await API.post(`/api/user/`, inputs);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('用户账户创建成功!');
|
||||
showSuccess('User account created successfully!');
|
||||
setInputs(originInputs);
|
||||
} else {
|
||||
showError(message);
|
||||
@ -30,13 +30,13 @@ const AddUser = () => {
|
||||
return (
|
||||
<>
|
||||
<Segment>
|
||||
<Header as="h3">创建新用户账户</Header>
|
||||
<Header as="h3">Create new user account</Header>
|
||||
<Form autoComplete="off">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label="用户名"
|
||||
label="Username"
|
||||
name="username"
|
||||
placeholder={'请输入用户名'}
|
||||
placeholder={'Please enter username'}
|
||||
onChange={handleInputChange}
|
||||
value={username}
|
||||
autoComplete="off"
|
||||
@ -45,9 +45,9 @@ const AddUser = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label="显示名称"
|
||||
label="Display name"
|
||||
name="display_name"
|
||||
placeholder={'请输入显示名称'}
|
||||
placeholder={'Please enter display name'}
|
||||
onChange={handleInputChange}
|
||||
value={display_name}
|
||||
autoComplete="off"
|
||||
@ -55,10 +55,10 @@ const AddUser = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label="密码"
|
||||
label="Password"
|
||||
name="password"
|
||||
type={'password'}
|
||||
placeholder={'请输入密码'}
|
||||
placeholder={'Please enter password'}
|
||||
onChange={handleInputChange}
|
||||
value={password}
|
||||
autoComplete="off"
|
||||
@ -66,7 +66,7 @@ const AddUser = () => {
|
||||
/>
|
||||
</Form.Field>
|
||||
<Button positive type={'submit'} onClick={submit}>
|
||||
提交
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</Segment>
|
||||
|
@ -76,7 +76,7 @@ const EditUser = () => {
|
||||
}
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('用户信息更新成功!');
|
||||
showSuccess('User information updated successfully!');
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -85,13 +85,13 @@ const EditUser = () => {
|
||||
return (
|
||||
<>
|
||||
<Segment loading={loading}>
|
||||
<Header as='h3'>更新用户信息</Header>
|
||||
<Header as='h3'>Update user information</Header>
|
||||
<Form autoComplete='new-password'>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='用户名'
|
||||
label='Username'
|
||||
name='username'
|
||||
placeholder={'请输入新的用户名'}
|
||||
placeholder={'Please enter a new username'}
|
||||
onChange={handleInputChange}
|
||||
value={username}
|
||||
autoComplete='new-password'
|
||||
@ -99,10 +99,10 @@ const EditUser = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='密码'
|
||||
label='Password'
|
||||
name='password'
|
||||
type={'password'}
|
||||
placeholder={'请输入新的密码,最短 8 位'}
|
||||
placeholder={'Please enter a new password, at least 8 characters'}
|
||||
onChange={handleInputChange}
|
||||
value={password}
|
||||
autoComplete='new-password'
|
||||
@ -110,9 +110,9 @@ const EditUser = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='显示名称'
|
||||
label='Display name'
|
||||
name='display_name'
|
||||
placeholder={'请输入新的显示名称'}
|
||||
placeholder={'Please enter a new display name'}
|
||||
onChange={handleInputChange}
|
||||
value={display_name}
|
||||
autoComplete='new-password'
|
||||
@ -122,14 +122,14 @@ const EditUser = () => {
|
||||
userId && <>
|
||||
<Form.Field>
|
||||
<Form.Dropdown
|
||||
label='分组'
|
||||
placeholder={'请选择分组'}
|
||||
label='Group'
|
||||
placeholder={'Please select a group'}
|
||||
name='group'
|
||||
fluid
|
||||
search
|
||||
selection
|
||||
allowAdditions
|
||||
additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
|
||||
additionLabel={'Please edit the group rate on the system settings page to add a new group:'}
|
||||
onChange={handleInputChange}
|
||||
value={inputs.group}
|
||||
autoComplete='new-password'
|
||||
@ -138,9 +138,9 @@ const EditUser = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label={`剩余额度${renderQuotaWithPrompt(quota)}`}
|
||||
label={`Remaining quota${renderQuotaWithPrompt(quota)}`}
|
||||
name='quota'
|
||||
placeholder={'请输入新的剩余额度'}
|
||||
placeholder={'Please enter a new remaining quota'}
|
||||
onChange={handleInputChange}
|
||||
value={quota}
|
||||
type={'number'}
|
||||
@ -151,36 +151,36 @@ const EditUser = () => {
|
||||
}
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='已绑定的 GitHub 账户'
|
||||
label='Bound GitHub account'
|
||||
name='github_id'
|
||||
value={github_id}
|
||||
autoComplete='new-password'
|
||||
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
|
||||
placeholder='This item is read-only, users need to bind through the relevant binding button on the personal settings page, cannot be directly modified'
|
||||
readOnly
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='已绑定的微信账户'
|
||||
label='Bound WeChat account'
|
||||
name='wechat_id'
|
||||
value={wechat_id}
|
||||
autoComplete='new-password'
|
||||
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
|
||||
placeholder='This item is read-only, users need to bind through the relevant binding button on the personal settings page, cannot be directly modified'
|
||||
readOnly
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label='已绑定的邮箱账户'
|
||||
label='Bound email account'
|
||||
name='email'
|
||||
value={email}
|
||||
autoComplete='new-password'
|
||||
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
|
||||
placeholder='This item is read-only, users need to bind through the relevant binding button on the personal settings page, cannot be directly modified'
|
||||
readOnly
|
||||
/>
|
||||
</Form.Field>
|
||||
<Button onClick={handleCancel}>取消</Button>
|
||||
<Button positive onClick={submit}>提交</Button>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
<Button positive onClick={submit}>Submit</Button>
|
||||
</Form>
|
||||
</Segment>
|
||||
</>
|
||||
|
@ -5,7 +5,7 @@ import UsersTable from '../../components/UsersTable';
|
||||
const User = () => (
|
||||
<>
|
||||
<Segment>
|
||||
<Header as='h3'>管理用户</Header>
|
||||
<Header as='h3'>Manage Users</Header>
|
||||
<UsersTable/>
|
||||
</Segment>
|
||||
</>
|
||||
|
Loading…
Reference in New Issue
Block a user