前端改为美元展示
This commit is contained in:
parent
fff889742b
commit
876e0d429b
23
README.md
23
README.md
@ -9,9 +9,10 @@ docker pull woodchen/czloapi
|
||||
- [x] 修改颜色
|
||||
- [x] 日志页面新增快速筛选日期、修复渠道ID查询、自动更新消耗额度、修改日志详情;
|
||||
- [x] 用户管理界面,支持快速设置用户组,在`web\src\components\UsersTable.js`处修改相关值;
|
||||
- [x] 令牌界面,删除无用的多种复制、聊天等按钮;
|
||||
- [x] 删除系统访问令牌;
|
||||
- [x] Key界面,删除无用的多种复制、聊天等按钮;
|
||||
- [x] 删除系统访问Key;
|
||||
- [x] 在个人设置页面显示分组,使用信息等;
|
||||
- [x] 前端全部以美金展示;
|
||||
- [ ] 渠道管理处,已启用渠道和禁用渠道,启用/禁用按钮改为不同颜色;
|
||||
|
||||
- [x] 等
|
||||
@ -113,7 +114,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用
|
||||
3. 支持通过**负载均衡**的方式访问多个渠道。
|
||||
4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。
|
||||
5. 支持**多机部署**,[详见此处](#多机部署)。
|
||||
6. 支持**令牌管理**,设置令牌的过期时间和额度。
|
||||
6. 支持**Key管理**,设置Key的过期时间和额度。
|
||||
7. 支持**兑换码管理**,支持批量生成和导出兑换码,可使用兑换码为账户进行充值。
|
||||
8. 支持**通道管理**,批量创建通道。
|
||||
9. 支持**用户分组**以及**渠道分组**,支持为不同分组设置不同的倍率。
|
||||
@ -129,7 +130,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用
|
||||
19. 支持丰富的**自定义**设置,
|
||||
1. 支持自定义系统名称,logo 以及页脚。
|
||||
2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
|
||||
20. 支持通过系统访问令牌访问管理 API。
|
||||
20. 支持通过系统访问Key访问管理 API。
|
||||
21. 支持 Cloudflare Turnstile 用户校验。
|
||||
22. 支持用户管理,支持**多种用户登录注册方式**:
|
||||
+ 邮箱登录注册(支持注册邮箱白名单)以及通过邮箱进行密码重置。
|
||||
@ -328,11 +329,11 @@ Render 可以直接部署 docker 镜像,不需要 fork 仓库:https://dashbo
|
||||
**Note**:如果你不知道某个配置项的含义,可以临时删掉值以看到进一步的提示文字。
|
||||
|
||||
## 使用方法
|
||||
在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增访问令牌。
|
||||
在`渠道`页面中添加你的 API Key,之后在`Key`页面中新增访问Key。
|
||||
|
||||
之后就可以使用你的令牌访问 CZL Oapi 了,使用方式与 [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) 一致。
|
||||
之后就可以使用你的Key访问 CZL Oapi 了,使用方式与 [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) 一致。
|
||||
|
||||
你需要在各种用到 OpenAI API 的地方设置 API Base 为你的 CZL Oapi 的部署地址,例如:`https://openai.justsong.cn`,API Key 则为你在 CZL Oapi 中生成的令牌。
|
||||
你需要在各种用到 OpenAI API 的地方设置 API Base 为你的 CZL Oapi 的部署地址,例如:`https://openai.justsong.cn`,API Key 则为你在 CZL Oapi 中生成的Key。
|
||||
|
||||
注意,具体的 API Base 的格式取决于你所使用的客户端。
|
||||
|
||||
@ -352,8 +353,8 @@ graph LR
|
||||
B -->|中继并修改请求体和返回体| F(非 OpenAI API 格式下游渠道)
|
||||
```
|
||||
|
||||
可以通过在令牌后面添加渠道 ID 的方式指定使用哪一个渠道处理本次请求,例如:`Authorization: Bearer ONE_API_KEY-CHANNEL_ID`。
|
||||
注意,需要是管理员用户创建的令牌才能指定渠道 ID。
|
||||
可以通过在Key后面添加渠道 ID 的方式指定使用哪一个渠道处理本次请求,例如:`Authorization: Bearer ONE_API_KEY-CHANNEL_ID`。
|
||||
注意,需要是管理员用户创建的Key才能指定渠道 ID。
|
||||
|
||||
不加的话将会使用负载均衡的方式使用多个渠道。
|
||||
|
||||
@ -426,8 +427,8 @@ https://openai.justsong.cn
|
||||
+ 如果是非流模式,官方接口会返回消耗的总 token,但是你要注意提示和补全的消耗倍率不一样。
|
||||
+ 注意,CZL Oapi 的默认倍率就是官方倍率,是已经调整过的。
|
||||
2. 账户额度足够为什么提示额度不足?
|
||||
+ 请检查你的令牌额度是否足够,这个和账户额度是分开的。
|
||||
+ 令牌额度仅供用户设置最大使用量,用户可自由设置。
|
||||
+ 请检查你的Key额度是否足够,这个和账户额度是分开的。
|
||||
+ Key额度仅供用户设置最大使用量,用户可自由设置。
|
||||
3. 提示无可用渠道?
|
||||
+ 请检查的用户分组和渠道分组设置。
|
||||
+ 以及渠道的模型设置。
|
||||
|
@ -437,7 +437,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
||||
if quota != 0 {
|
||||
// logContent := fmt.Sprintf("模型倍率 %.4f", modelRatio)
|
||||
ratio = ratio * 0.002
|
||||
logContent := fmt.Sprintf("提示: $%.10g/1k tokens", ratio)
|
||||
logContent := fmt.Sprintf("输入: $%.10g/1k tokens", ratio)
|
||||
model.RecordConsumeLog(ctx, userId, channelId, promptTokens, completionTokens, textRequest.Model, tokenName, quota, logContent)
|
||||
model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
|
||||
model.UpdateChannelUsedQuota(channelId, quota)
|
||||
|
@ -112,7 +112,7 @@ func AddToken(c *gin.Context) {
|
||||
if len(token.Name) > 30 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌名称过长",
|
||||
"message": "Key名称过长",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -174,7 +174,7 @@ func UpdateToken(c *gin.Context) {
|
||||
if len(token.Name) > 30 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌名称过长",
|
||||
"message": "Key名称过长",
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -190,14 +190,14 @@ func UpdateToken(c *gin.Context) {
|
||||
if cleanToken.Status == common.TokenStatusExpired && cleanToken.ExpiredTime <= common.GetTimestamp() && cleanToken.ExpiredTime != -1 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期",
|
||||
"message": "Key已过期,无法启用,请先修改Key过期时间,或者设置为永不过期",
|
||||
})
|
||||
return
|
||||
}
|
||||
if cleanToken.Status == common.TokenStatusExhausted && cleanToken.RemainQuota <= 0 && !cleanToken.UnlimitedQuota {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度",
|
||||
"message": "Key可用额度已用尽,无法启用,请先修改Key剩余额度,或者设置为无限额度",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
60
i18n/en.json
60
i18n/en.json
@ -38,11 +38,11 @@
|
||||
"兑换码名称长度必须在1-20之间": "The length of the redemption code name must be between 1-20",
|
||||
"兑换码个数必须大于0": "The number of redemption codes must be greater than 0",
|
||||
"一次兑换码批量生成的个数不能大于 100": "The number of redemption codes generated in a batch cannot be greater than 100",
|
||||
"通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)": "Using model %s with token %s consumes %s (model rate %.2f, group rate %.2f)",
|
||||
"通过Key「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)": "Using model %s with token %s consumes %s (model rate %.2f, group rate %.2f)",
|
||||
"当前分组上游负载已饱和,请稍后再试": "The current group load is saturated, please try again later",
|
||||
"令牌名称过长": "Token name is too long",
|
||||
"令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.",
|
||||
"令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度": "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",
|
||||
"Key名称过长": "Token name is too long",
|
||||
"Key已过期,无法启用,请先修改Key过期时间,或者设置为永不过期": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.",
|
||||
"Key可用额度已用尽,无法启用,请先修改Key剩余额度,或者设置为无限额度": "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",
|
||||
"管理员关闭了密码登录": "The administrator has turned off password login",
|
||||
"无法保存会话信息,请重试": "Unable to save session information, please try again",
|
||||
"管理员关闭了通过密码进行注册,请使用第三方账户验证的形式进行注册": "The administrator has turned off registration via password. Please use the form of third-party account verification to register",
|
||||
@ -81,14 +81,14 @@
|
||||
"无效的兑换码": "Invalid redemption code",
|
||||
"该兑换码已被使用": "The redemption code has been used",
|
||||
"通过兑换码充值 %s": "Recharge %s through redemption code",
|
||||
"未提供令牌": "No token provided",
|
||||
"该令牌状态不可用": "The token status is not available",
|
||||
"该令牌已过期": "The token has expired",
|
||||
"该令牌额度已用尽": "The token quota has been used up",
|
||||
"无效的令牌": "Invalid token",
|
||||
"未提供Key": "No token provided",
|
||||
"该Key状态不可用": "The token status is not available",
|
||||
"该Key已过期": "The token has expired",
|
||||
"该Key额度已用尽": "The token quota has been used up",
|
||||
"无效的Key": "Invalid token",
|
||||
"id 或 userId 为空!": "id or userId is empty!",
|
||||
"quota 不能为负数!": "quota cannot be negative!",
|
||||
"令牌额度不足": "Insufficient token quota",
|
||||
"Key额度不足": "Insufficient token quota",
|
||||
"用户额度不足": "Insufficient user quota",
|
||||
"您的额度即将用尽": "Your quota is about to run out",
|
||||
"您的额度已用尽": "Your quota has been used up",
|
||||
@ -149,7 +149,7 @@
|
||||
"出现错误,第 ${count} 次重试中...": "An error occurred, retrying for the ${count} time...",
|
||||
"首页": "Home",
|
||||
"渠道": "Channel",
|
||||
"令牌": "Token",
|
||||
"Key": "Token",
|
||||
"兑换": "Redeem",
|
||||
"充值": "Recharge",
|
||||
"用户": "User",
|
||||
@ -199,7 +199,7 @@
|
||||
"一单位货币能兑换的额度": "Quota that can be exchanged for one unit of currency",
|
||||
"启用额度消费日志记录": "Enable quota consumption log recording",
|
||||
"以货币形式显示额度": "Display quota in the form of currency",
|
||||
"相关 API 显示令牌额度而非用户额度": "Related API displays token quota instead of user quota",
|
||||
"相关 API 显示Key额度而非用户额度": "Related API displays token quota instead of user quota",
|
||||
"保存通用设置": "Save General Settings",
|
||||
"监控设置": "Monitoring Settings",
|
||||
"最长响应时间": "Longest Response Time",
|
||||
@ -257,17 +257,17 @@
|
||||
"重置邮件发送成功": "Reset mail sent successfully",
|
||||
"请检查邮箱": "Please check your email",
|
||||
"密码重置": "Password Reset",
|
||||
"令牌已重置并已复制到剪贴板": "Token has been reset and copied to clipboard",
|
||||
"Key已重置并已复制到剪贴板": "Token has been reset and copied to clipboard",
|
||||
"邀请链接已复制到剪切板": "Invitation link has been copied to clipboard",
|
||||
"微信账户绑定成功": "WeChat account binding succeeded",
|
||||
"验证码发送成功": "Verification code sent successfully",
|
||||
"邮箱账户绑定成功": "Email account binding succeeded",
|
||||
"注意": "Note",
|
||||
"此处生成的令牌用于系统管理": "The token generated here is used for system management",
|
||||
"此处生成的Key用于系统管理": "The token generated here is used for system management",
|
||||
"而非用于请求 OpenAI 相关的服务": "Not for requesting OpenAI related services",
|
||||
"请知悉": "Please be aware",
|
||||
"更新个人信息": "Update Personal Information",
|
||||
"生成系统访问令牌": "Generate System Access Token",
|
||||
"生成系统访问Key": "Generate System Access Token",
|
||||
"复制邀请链接": "Copy Invitation Link",
|
||||
"账号绑定": "Account Binding",
|
||||
"绑定微信账号": "Bind WeChat Account",
|
||||
@ -348,16 +348,16 @@
|
||||
"保存 Turnstile 设置": "Save Turnstile Settings",
|
||||
"已过期": "Expired",
|
||||
"已耗尽": "Exhausted",
|
||||
"搜索令牌的名称 ...": "Search for the name of the token...",
|
||||
"搜索Key的名称 ...": "Search for the name of the token...",
|
||||
"已用额度": "Quota used",
|
||||
"剩余额度": "Remaining quota",
|
||||
"过期时间": "Expiration time",
|
||||
"无": "None",
|
||||
"无限制": "Unlimited",
|
||||
"永不过期": "Never expires",
|
||||
"无法复制到剪贴板,请手动复制,已将令牌填入搜索框": "Unable to copy to clipboard, please copy manually, the token has been entered into the search box",
|
||||
"删除令牌": "Delete Token",
|
||||
"添加新的令牌": "Add New Token",
|
||||
"无法复制到剪贴板,请手动复制,已将Key填入搜索框": "Unable to copy to clipboard, please copy manually, the token has been entered into the search box",
|
||||
"删除Key": "Delete Token",
|
||||
"添加新的Key": "Add New Token",
|
||||
"普通用户": "Regular User",
|
||||
"管理员": "Admin",
|
||||
"超级管理员": "Super Admin",
|
||||
@ -410,7 +410,7 @@
|
||||
"请输入密钥": "Please enter the key",
|
||||
"批量创建": "Batch Create",
|
||||
"更新渠道信息": "Update Channel Information",
|
||||
"我的令牌": "My Tokens",
|
||||
"我的Key": "My Tokens",
|
||||
"管理兑换码": "Manage Redeem Codes",
|
||||
"兑换码": "Redeem Code",
|
||||
"管理用户": "Manage Users",
|
||||
@ -430,16 +430,16 @@
|
||||
"一天后过期": "Expires after one day",
|
||||
"一小时后过期": "Expires after one hour",
|
||||
"一分钟后过期": "Expires after one minute",
|
||||
"创建新的令牌": "Create New Token",
|
||||
"注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。": "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.",
|
||||
"创建新的Key": "Create New Token",
|
||||
"注意,Key的额度仅用于限制Key本身的最大额度使用量,实际的使用受到账户的剩余额度限制。": "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.",
|
||||
"设为无限额度": "Set to unlimited quota",
|
||||
"更新令牌信息": "Update Token Information",
|
||||
"更新Key信息": "Update Token Information",
|
||||
"请输入充值码!": "Please enter the recharge code!",
|
||||
"请输入名称": "Please enter a name",
|
||||
"请输入密钥,一行一个": "Please enter the key, one per line",
|
||||
"请输入额度": "Please enter the quota",
|
||||
"令牌创建成功": "Token created successfully",
|
||||
"令牌更新成功": "Token updated successfully",
|
||||
"Key创建成功": "Token created successfully",
|
||||
"Key更新成功": "Token updated successfully",
|
||||
"充值成功!": "Recharge successful!",
|
||||
"更新用户信息": "Update User Information",
|
||||
"请输入新的用户名": "Please enter a new username",
|
||||
@ -455,16 +455,16 @@
|
||||
"模型倍率 %.2f,分组倍率 %.2f": "model rate %.2f, group rate %.2f",
|
||||
"使用明细(总消耗额度:{renderQuota(stat.quota)})": "Usage Details (Total Consumption Quota: {renderQuota(stat.quota)})",
|
||||
"用户名称": "User Name",
|
||||
"令牌名称": "Token Name",
|
||||
"Key名称": "Token Name",
|
||||
"留空则查询全部用户": "Leave blank to query all users",
|
||||
"留空则查询全部令牌": "Leave blank to query all tokens",
|
||||
"留空则查询全部Key": "Leave blank to query all tokens",
|
||||
"模型名称": "Model Name",
|
||||
"留空则查询全部模型": "Leave blank to query all models",
|
||||
"起始时间": "Start Time",
|
||||
"结束时间": "End Time",
|
||||
"查询": "Query",
|
||||
"提示": "Prompt",
|
||||
"补全": "Completion",
|
||||
"输入": "Prompt",
|
||||
"输出": "Completion",
|
||||
"消耗额度": "Used Quota",
|
||||
"可选值": "Optional Values",
|
||||
"渠道不存在:%d": "Channel does not exist: %d",
|
||||
@ -516,7 +516,7 @@
|
||||
"请输入渠道对应的鉴权密钥": "Please enter the authentication key corresponding to the channel",
|
||||
"注意,": "Note that, ",
|
||||
",图片演示。": "related image demo.",
|
||||
"令牌创建成功,请在列表页面点击复制获取令牌!": "Token created successfully, please click copy on the list page to get the token!",
|
||||
"Key创建成功,请在列表页面点击复制获取Key!": "Token created successfully, please click copy on the list page to get the token!",
|
||||
"代理": "Proxy",
|
||||
"此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com",
|
||||
"取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?": "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?",
|
||||
|
@ -35,17 +35,17 @@ 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("未提供Key")
|
||||
}
|
||||
token, err = CacheGetTokenByKey(key)
|
||||
if err == nil {
|
||||
if token.Status == common.TokenStatusExhausted {
|
||||
return nil, errors.New("该令牌额度已用尽")
|
||||
return nil, errors.New("该Key额度已用尽")
|
||||
} else if token.Status == common.TokenStatusExpired {
|
||||
return nil, errors.New("该令牌已过期")
|
||||
return nil, errors.New("该Key已过期")
|
||||
}
|
||||
if token.Status != common.TokenStatusEnabled {
|
||||
return nil, errors.New("该令牌状态不可用")
|
||||
return nil, errors.New("该Key状态不可用")
|
||||
}
|
||||
if token.ExpiredTime != -1 && token.ExpiredTime < common.GetTimestamp() {
|
||||
if !common.RedisEnabled {
|
||||
@ -55,7 +55,7 @@ func ValidateUserToken(key string) (token *Token, err error) {
|
||||
common.SysError("failed to update token status" + err.Error())
|
||||
}
|
||||
}
|
||||
return nil, errors.New("该令牌已过期")
|
||||
return nil, errors.New("该Key已过期")
|
||||
}
|
||||
if !token.UnlimitedQuota && token.RemainQuota <= 0 {
|
||||
if !common.RedisEnabled {
|
||||
@ -66,11 +66,11 @@ func ValidateUserToken(key string) (token *Token, err error) {
|
||||
common.SysError("failed to update token status" + err.Error())
|
||||
}
|
||||
}
|
||||
return nil, errors.New("该令牌额度已用尽")
|
||||
return nil, errors.New("该Key额度已用尽")
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
return nil, errors.New("无效的令牌")
|
||||
return nil, errors.New("无效的Key")
|
||||
}
|
||||
|
||||
func GetTokenByIds(id int, userId int) (*Token, error) {
|
||||
@ -183,7 +183,7 @@ func PreConsumeTokenQuota(tokenId int, quota int) (err error) {
|
||||
return err
|
||||
}
|
||||
if !token.UnlimitedQuota && token.RemainQuota < quota {
|
||||
return errors.New("令牌额度不足")
|
||||
return errors.New("Key额度不足")
|
||||
}
|
||||
userQuota, err := GetUserQuota(token.UserId)
|
||||
if err != nil {
|
||||
|
@ -22,7 +22,7 @@ let headerButtons = [
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '令牌',
|
||||
name: 'Key',
|
||||
to: '/token',
|
||||
icon: 'key',
|
||||
color: 'var(--czl-primary-color)'
|
||||
|
@ -276,7 +276,7 @@ const LogsTable = () => {
|
||||
|
||||
<Form>
|
||||
<Form.Group>
|
||||
<Form.Input fluid label={'令牌名称'} width={3} value={token_name}
|
||||
<Form.Input fluid label={'Key名称'} width={3} value={token_name}
|
||||
placeholder={'可选值'} name='token_name' onChange={handleInputChange} />
|
||||
<Form.Input fluid label='模型名称' width={3} value={model_name} placeholder='可选值'
|
||||
name='model_name'
|
||||
@ -360,7 +360,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
令牌
|
||||
Key
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -387,7 +387,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
提示
|
||||
输入
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -396,7 +396,7 @@ const LogsTable = () => {
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
补全
|
||||
输出
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
|
@ -200,7 +200,7 @@ const OperationSetting = () => {
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.DisplayTokenStatEnabled === 'true'}
|
||||
label='Billing 相关 API 显示令牌额度而非用户额度'
|
||||
label='Billing 相关 API 显示Key额度而非用户额度'
|
||||
name='DisplayTokenStatEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
|
@ -131,7 +131,7 @@ const PersonalSetting = () => {
|
||||
const handleSystemTokenClick = async (e) => {
|
||||
e.target.select();
|
||||
await copy(e.target.value);
|
||||
showSuccess(`系统令牌已复制到剪切板`);
|
||||
showSuccess(`系统Key已复制到剪切板`);
|
||||
};
|
||||
|
||||
const deleteAccount = async () => {
|
||||
@ -228,12 +228,12 @@ const PersonalSetting = () => {
|
||||
<Divider />
|
||||
<Header as='h3'>通用设置</Header>
|
||||
{/* <Message>
|
||||
注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。
|
||||
注意,此处生成的Key用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。
|
||||
</Message> */}
|
||||
<Button as={Link} to={`/user/edit/`}>
|
||||
更新个人信息
|
||||
</Button>
|
||||
{/* <Button onClick={generateAccessToken}>生成系统访问令牌</Button> */}
|
||||
{/* <Button onClick={generateAccessToken}>生成系统访问Key</Button> */}
|
||||
<Button onClick={getAffLink}>复制邀请链接</Button>
|
||||
|
||||
{systemToken && (
|
||||
|
@ -109,7 +109,7 @@ const TokensTable = () => {
|
||||
if (await copy(url)) {
|
||||
showSuccess('已复制到剪贴板!');
|
||||
} else {
|
||||
showWarning('无法复制到剪贴板,请手动复制,已将令牌填入搜索框。');
|
||||
showWarning('无法复制到剪贴板,请手动复制,已将Key填入搜索框。');
|
||||
setSearchKeyword(url);
|
||||
}
|
||||
};
|
||||
@ -241,7 +241,7 @@ const TokensTable = () => {
|
||||
icon='search'
|
||||
fluid
|
||||
iconPosition='left'
|
||||
placeholder='搜索令牌的名称 ...'
|
||||
placeholder='搜索Key的名称 ...'
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={handleKeywordChange}
|
||||
@ -349,7 +349,7 @@ const TokensTable = () => {
|
||||
}}
|
||||
style={{ backgroundColor: 'var(--czl-error-color)', borderColor: 'var(--czl-error-color)' }}
|
||||
>
|
||||
删除令牌 {token.name}
|
||||
删除Key {token.name}
|
||||
</Button>
|
||||
</Popup>
|
||||
<Button
|
||||
@ -390,7 +390,7 @@ const TokensTable = () => {
|
||||
loading={loading}
|
||||
style={{ color: "var(--czl-main)", backgroundColor: "var(--czl-link-color)" }}
|
||||
>
|
||||
添加新的令牌
|
||||
添加新的Key
|
||||
</Button>
|
||||
<Button size='small' onClick={refresh} loading={loading}>刷新</Button>
|
||||
|
||||
|
@ -37,22 +37,26 @@ export function renderNumber(num) {
|
||||
}
|
||||
}
|
||||
|
||||
export function renderQuota(quota, digits = 2) {
|
||||
export function renderQuota(quota, digits = 10) {
|
||||
let quotaPerUnit = localStorage.getItem('quota_per_unit');
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
quotaPerUnit = parseFloat(quotaPerUnit);
|
||||
displayInCurrency = displayInCurrency === 'true';
|
||||
if (displayInCurrency) {
|
||||
return '$' + (quota / quotaPerUnit).toFixed(digits);
|
||||
let displayValue = (quota / quotaPerUnit).toFixed(digits);
|
||||
// 去除尾部多余的零
|
||||
displayValue = parseFloat(displayValue).toString();
|
||||
return '$' + displayValue;
|
||||
}
|
||||
return renderNumber(quota);
|
||||
}
|
||||
|
||||
|
||||
export function renderQuotaWithPrompt(quota, digits) {
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
displayInCurrency = displayInCurrency === 'true';
|
||||
if (displayInCurrency) {
|
||||
return `(等价金额:${renderQuota(quota, digits)})`;
|
||||
return `(金额:${renderQuota(quota, digits)})`;
|
||||
}
|
||||
return '';
|
||||
}
|
@ -53,6 +53,7 @@ const EditChannel = () => {
|
||||
const [groupOptions, setGroupOptions] = useState([]);
|
||||
const [basicModels, setBasicModels] = useState([]);
|
||||
const [basicNoGPTModels, setBasicNoGPTModels] = useState([]);
|
||||
const [fullNo32KOPENAIModels, setfullNo32KOPENAIModels] = useState([]);
|
||||
const [fullOPENAIModels, setFullOPENAIModels] = useState([]);
|
||||
const [customModel, setCustomModel] = useState('');
|
||||
const handleInputChange = (e, { name, value }) => {
|
||||
@ -126,6 +127,10 @@ const EditChannel = () => {
|
||||
return (model.id.startsWith('gpt-') || model.id.startsWith('text-') || model.id.startsWith('dall-') || model.id.startsWith('whisper-') || model.id.startsWith('code-')) && !model.id.startsWith('text-embedding-v1');
|
||||
}).map((model) => model.id));
|
||||
|
||||
setfullNo32KOPENAIModels(res.data.data.filter((model) => {
|
||||
return (model.id.startsWith('gpt-') || model.id.startsWith('text-') || model.id.startsWith('dall-') || model.id.startsWith('whisper-') || model.id.startsWith('code-')) && !model.id.startsWith('text-embedding-v1') && !model.id.startsWith('gpt-4-32k');
|
||||
}).map((model) => model.id));
|
||||
|
||||
setBasicModels(res.data.data.filter((model) => {
|
||||
return (model.id.startsWith('gpt-3') || model.id.startsWith('text-') || model.id.startsWith('dall-') || model.id.startsWith('whisper-') || model.id.startsWith('code-')) && !model.id.startsWith('text-embedding-v1');
|
||||
}).map((model) => model.id));
|
||||
@ -368,18 +373,21 @@ const EditChannel = () => {
|
||||
/>
|
||||
</Form.Field>
|
||||
<div style={{ lineHeight: '40px', marginBottom: '12px' }}>
|
||||
<Button type={'button'} onClick={() => {
|
||||
handleInputChange(null, { name: 'models', value: basicModels });
|
||||
}}>填入基础OPENAI模型</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
handleInputChange(null, { name: 'models', value: basicNoGPTModels });
|
||||
}}>填入基础无gpt模型</Button>
|
||||
}}>基础无gpt模型</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
handleInputChange(null, { name: 'models', value: basicModels });
|
||||
}}>基础OPENAI模型</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
handleInputChange(null, { name: 'models', value: fullNo32KOPENAIModels });
|
||||
}}>无32K OPENAI模型</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
handleInputChange(null, { name: 'models', value: fullOPENAIModels });
|
||||
}}>填入所有OPENAI模型</Button>
|
||||
}}>OPENAI模型</Button>
|
||||
<Button type={'button'} onClick={() => {
|
||||
handleInputChange(null, { name: 'models', value: [] });
|
||||
}}>清除所有模型</Button>
|
||||
}}>清除</Button>
|
||||
<Input
|
||||
action={
|
||||
<Button type={'button'} onClick={addCustomModel}>填入</Button>
|
||||
|
@ -12,7 +12,7 @@ const EditRedemption = () => {
|
||||
const [loading, setLoading] = useState(isEdit);
|
||||
const originInputs = {
|
||||
name: '',
|
||||
quota: 100000,
|
||||
quota: 100,
|
||||
count: 1
|
||||
};
|
||||
const [inputs, setInputs] = useState(originInputs);
|
||||
@ -30,6 +30,8 @@ const EditRedemption = () => {
|
||||
let res = await API.get(`/api/redemption/${redemptionId}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
let quotaPerUnit = parseFloat(localStorage.getItem('quota_per_unit'));
|
||||
data.quota = data.quota / quotaPerUnit;
|
||||
setInputs(data);
|
||||
} else {
|
||||
showError(message);
|
||||
@ -46,7 +48,8 @@ const EditRedemption = () => {
|
||||
if (!isEdit && inputs.name === '') return;
|
||||
let localInputs = inputs;
|
||||
localInputs.count = parseInt(localInputs.count);
|
||||
localInputs.quota = parseInt(localInputs.quota);
|
||||
let quotaPerUnit = parseFloat(localStorage.getItem('quota_per_unit'));
|
||||
localInputs.quota = Math.ceil(parseFloat(localInputs.quota) * quotaPerUnit);
|
||||
let res;
|
||||
if (isEdit) {
|
||||
res = await API.put(`/api/redemption/`, { ...localInputs, id: parseInt(redemptionId) });
|
||||
@ -93,7 +96,8 @@ const EditRedemption = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label={`额度${renderQuotaWithPrompt(quota)}`}
|
||||
label={`额度(单位:美金)`}
|
||||
// ${renderQuotaWithPrompt(quota)}
|
||||
name='quota'
|
||||
placeholder={'请输入单个兑换码中包含的额度'}
|
||||
onChange={handleInputChange}
|
||||
|
@ -11,7 +11,7 @@ const EditToken = () => {
|
||||
const [loading, setLoading] = useState(isEdit);
|
||||
const originInputs = {
|
||||
name: '',
|
||||
remain_quota: isEdit ? 0 : 500000,
|
||||
remain_quota: isEdit ? 0 : renderQuota(500000),
|
||||
expired_time: -1,
|
||||
unlimited_quota: false
|
||||
};
|
||||
@ -50,6 +50,8 @@ const EditToken = () => {
|
||||
if (data.expired_time !== -1) {
|
||||
data.expired_time = timestamp2string(data.expired_time);
|
||||
}
|
||||
let quotaPerUnit = parseFloat(localStorage.getItem('quota_per_unit'));
|
||||
data.remain_quota = data.remain_quota / quotaPerUnit;
|
||||
setInputs(data);
|
||||
} else {
|
||||
showError(message);
|
||||
@ -65,7 +67,8 @@ const EditToken = () => {
|
||||
const submit = async () => {
|
||||
if (!isEdit && inputs.name === '') return;
|
||||
let localInputs = inputs;
|
||||
localInputs.remain_quota = parseInt(localInputs.remain_quota);
|
||||
let quotaPerUnit = parseFloat(localStorage.getItem('quota_per_unit'));
|
||||
localInputs.remain_quota = Math.ceil(parseFloat(localInputs.remain_quota) * quotaPerUnit);
|
||||
if (localInputs.expired_time !== -1) {
|
||||
let time = Date.parse(localInputs.expired_time);
|
||||
if (isNaN(time)) {
|
||||
@ -83,9 +86,9 @@ const EditToken = () => {
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
if (isEdit) {
|
||||
showSuccess('令牌更新成功!');
|
||||
showSuccess('Key更新成功!');
|
||||
} else {
|
||||
showSuccess('令牌创建成功,请在列表页面点击复制获取令牌!');
|
||||
showSuccess('Key创建成功,请在列表页面点击复制获取Key!');
|
||||
setInputs(originInputs);
|
||||
}
|
||||
} else {
|
||||
@ -96,7 +99,7 @@ const EditToken = () => {
|
||||
return (
|
||||
<>
|
||||
<Segment loading={loading}>
|
||||
<Header as='h3'>{isEdit ? '更新令牌信息' : '创建新的令牌'}</Header>
|
||||
<Header as='h3'>{isEdit ? '更新Key信息' : '创建新的Key'}</Header>
|
||||
<Form autoComplete='new-password'>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
@ -137,10 +140,10 @@ const EditToken = () => {
|
||||
setExpiredTime(0, 0, 0, 1);
|
||||
}}>一分钟后过期</Button>
|
||||
</div>
|
||||
<Message>注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。</Message>
|
||||
<Message>注意,Key的额度仅用于限制Key本身的最大额度使用量,实际的使用受到账户的剩余额度限制。</Message>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label={`额度${renderQuotaWithPrompt(remain_quota)}`}
|
||||
label={`额度(单位:美金)`}
|
||||
name='remain_quota'
|
||||
placeholder={'请输入额度'}
|
||||
onChange={handleInputChange}
|
||||
|
@ -5,7 +5,7 @@ import TokensTable from '../../components/TokensTable';
|
||||
const Token = () => (
|
||||
<>
|
||||
<Segment>
|
||||
<Header as='h3'>我的令牌</Header>
|
||||
<Header as='h3'>我的Key</Header>
|
||||
<TokensTable/>
|
||||
</Segment>
|
||||
</>
|
||||
|
@ -50,6 +50,8 @@ const EditUser = () => {
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
data.password = '';
|
||||
let quotaPerUnit = parseFloat(localStorage.getItem('quota_per_unit'));
|
||||
data.quota = data.quota / quotaPerUnit;
|
||||
setInputs(data);
|
||||
} else {
|
||||
showError(message);
|
||||
@ -67,6 +69,8 @@ const EditUser = () => {
|
||||
let res = undefined;
|
||||
if (userId) {
|
||||
let data = { ...inputs, id: parseInt(userId) };
|
||||
let quotaPerUnit = parseFloat(localStorage.getItem('quota_per_unit'));
|
||||
data.quota = Math.ceil(parseFloat(data.quota) * quotaPerUnit);
|
||||
if (typeof data.quota === 'string') {
|
||||
data.quota = parseInt(data.quota);
|
||||
}
|
||||
@ -138,7 +142,7 @@ const EditUser = () => {
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
label={`剩余额度${renderQuotaWithPrompt(quota)}`}
|
||||
label={`剩余额度(单位:美金)`}
|
||||
name='quota'
|
||||
placeholder={'请输入新的剩余额度'}
|
||||
onChange={handleInputChange}
|
||||
|
Loading…
Reference in New Issue
Block a user