Commit a6989dfb by Yan Committed by GitHub

Merge branch 'Calcium-Ion:main' into main

parents 411d3a62 d429483f
...@@ -97,6 +97,7 @@ func FetchUpstreamModels(c *gin.Context) { ...@@ -97,6 +97,7 @@ func FetchUpstreamModels(c *gin.Context) {
}) })
return return
} }
channel, err := model.GetChannelById(id, true) channel, err := model.GetChannelById(id, true)
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
...@@ -105,34 +106,35 @@ func FetchUpstreamModels(c *gin.Context) { ...@@ -105,34 +106,35 @@ func FetchUpstreamModels(c *gin.Context) {
}) })
return return
} }
if channel.Type != common.ChannelTypeOpenAI {
c.JSON(http.StatusOK, gin.H{ //if channel.Type != common.ChannelTypeOpenAI {
"success": false, // c.JSON(http.StatusOK, gin.H{
"message": "仅支持 OpenAI 类型渠道", // "success": false,
}) // "message": "仅支持 OpenAI 类型渠道",
return // })
} // return
url := fmt.Sprintf("%s/v1/models", *channel.BaseURL) //}
baseURL := common.ChannelBaseURLs[channel.Type]
if channel.GetBaseURL() == "" {
channel.BaseURL = &baseURL
}
url := fmt.Sprintf("%s/v1/models", baseURL)
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key)) body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
"message": err.Error(), "message": err.Error(),
}) })
return
} }
result := OpenAIModelsResponse{}
err = json.Unmarshal(body, &result) var result OpenAIModelsResponse
if err != nil { if err = json.Unmarshal(body, &result); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
}
if !result.Success {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
"message": "上游返回错误", "message": fmt.Sprintf("解析响应失败: %s", err.Error()),
}) })
return
} }
var ids []string var ids []string
...@@ -492,3 +494,79 @@ func UpdateChannel(c *gin.Context) { ...@@ -492,3 +494,79 @@ func UpdateChannel(c *gin.Context) {
}) })
return return
} }
func FetchModels(c *gin.Context) {
var req struct {
BaseURL string `json:"base_url"`
Key string `json:"key"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "Invalid request",
})
return
}
baseURL := req.BaseURL
if baseURL == "" {
baseURL = "https://api.openai.com"
}
client := &http.Client{}
url := fmt.Sprintf("%s/v1/models", baseURL)
request, err := http.NewRequest("GET", url, nil)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": err.Error(),
})
return
}
request.Header.Set("Authorization", "Bearer "+req.Key)
response, err := client.Do(request)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": err.Error(),
})
return
}
//check status code
if response.StatusCode != http.StatusOK {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to fetch models",
})
return
}
defer response.Body.Close()
var result struct {
Data []struct {
ID string `json:"id"`
} `json:"data"`
}
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": err.Error(),
})
return
}
var models []string
for _, model := range result.Data {
models = append(models, model.ID)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": models,
})
}
...@@ -8,10 +8,35 @@ import ( ...@@ -8,10 +8,35 @@ import (
func GetPricing(c *gin.Context) { func GetPricing(c *gin.Context) {
pricing := model.GetPricing() pricing := model.GetPricing()
userId, exists := c.Get("id")
usableGroup := map[string]string{}
groupRatio := common.GroupRatio
var group string
if exists {
user, err := model.GetChannelById(userId.(int), false)
if err != nil {
c.JSON(200, gin.H{
"success": false,
"message": err.Error(),
})
return
}
group = user.Group
}
usableGroup = common.GetUserUsableGroups(group)
// check groupRatio contains usableGroup
for group := range common.GroupRatio {
if _, ok := usableGroup[group]; !ok {
delete(groupRatio, group)
}
}
c.JSON(200, gin.H{ c.JSON(200, gin.H{
"success": true, "success": true,
"data": pricing, "data": pricing,
"group_ratio": common.GroupRatio, "group_ratio": groupRatio,
"usable_group": usableGroup,
}) })
} }
......
...@@ -98,6 +98,7 @@ func SetApiRouter(router *gin.Engine) { ...@@ -98,6 +98,7 @@ func SetApiRouter(router *gin.Engine) {
channelRoute.POST("/batch", controller.DeleteChannelBatch) channelRoute.POST("/batch", controller.DeleteChannelBatch)
channelRoute.POST("/fix", controller.FixChannelsAbilities) channelRoute.POST("/fix", controller.FixChannelsAbilities)
channelRoute.GET("/fetch_models/:id", controller.FetchUpstreamModels) channelRoute.GET("/fetch_models/:id", controller.FetchUpstreamModels)
channelRoute.POST("/fetch_models", controller.FetchModels)
} }
tokenRoute := apiRouter.Group("/token") tokenRoute := apiRouter.Group("/token")
......
...@@ -162,10 +162,12 @@ const ModelPricing = () => { ...@@ -162,10 +162,12 @@ const ModelPricing = () => {
title: t('可用分组'), title: t('可用分组'),
dataIndex: 'enable_groups', dataIndex: 'enable_groups',
render: (text, record, index) => { render: (text, record, index) => {
// enable_groups is a string array // enable_groups is a string array
return ( return (
<Space> <Space>
{text.map((group) => { {text.map((group) => {
if (usableGroup[group]) {
if (group === selectedGroup) { if (group === selectedGroup) {
return ( return (
<Tag <Tag
...@@ -193,6 +195,7 @@ const ModelPricing = () => { ...@@ -193,6 +195,7 @@ const ModelPricing = () => {
</Tag> </Tag>
); );
} }
}
})} })}
</Space> </Space>
); );
...@@ -275,6 +278,7 @@ const ModelPricing = () => { ...@@ -275,6 +278,7 @@ const ModelPricing = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [userState, userDispatch] = useContext(UserContext); const [userState, userDispatch] = useContext(UserContext);
const [groupRatio, setGroupRatio] = useState({}); const [groupRatio, setGroupRatio] = useState({});
const [usableGroup, setUsableGroup] = useState({});
const setModelsFormat = (models, groupRatio) => { const setModelsFormat = (models, groupRatio) => {
for (let i = 0; i < models.length; i++) { for (let i = 0; i < models.length; i++) {
...@@ -309,9 +313,10 @@ const ModelPricing = () => { ...@@ -309,9 +313,10 @@ const ModelPricing = () => {
let url = ''; let url = '';
url = `/api/pricing`; url = `/api/pricing`;
const res = await API.get(url); const res = await API.get(url);
const { success, message, data, group_ratio } = res.data; const { success, message, data, group_ratio, usable_group } = res.data;
if (success) { if (success) {
setGroupRatio(group_ratio); setGroupRatio(group_ratio);
setUsableGroup(usable_group);
setSelectedGroup(userState.user ? userState.user.group : 'default') setSelectedGroup(userState.user ? userState.user.group : 'default')
setModelsFormat(data, group_ratio); setModelsFormat(data, group_ratio);
} else { } else {
......
...@@ -430,6 +430,8 @@ ...@@ -430,6 +430,8 @@
"一小时后过期": "Expires after one hour", "一小时后过期": "Expires after one hour",
"一分钟后过期": "Expires after one minute", "一分钟后过期": "Expires after one minute",
"创建新的令牌": "Create New Token", "创建新的令牌": "Create New Token",
"令牌分组,默认为用户的分组": "Token group, default is the your's group",
"IP白名单(请勿过度信任此功能)": "IP whitelist (do not overly trust this function)",
"注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。": "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.", "注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。": "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", "设为无限额度": "Set to unlimited quota",
"更新令牌信息": "Update Token Information", "更新令牌信息": "Update Token Information",
...@@ -866,8 +868,8 @@ ...@@ -866,8 +868,8 @@
"请选择模式": "Please select mode", "请选择模式": "Please select mode",
"图片代理方式": "Picture agency method", "图片代理方式": "Picture agency method",
"用于替换 https://cdn.discordapp.com 的域名": "The domain name used to replace https://cdn.discordapp.com", "用于替换 https://cdn.discordapp.com 的域名": "The domain name used to replace https://cdn.discordapp.com",
"一个月": "a month", "一个月": "A month",
"一天": "one day", "一天": "One day",
"令牌渠道分组选择": "Token channel grouping selection", "令牌渠道分组选择": "Token channel grouping selection",
"只可使用对应分组包含的模型。": "Only models contained in the corresponding group can be used.", "只可使用对应分组包含的模型。": "Only models contained in the corresponding group can be used.",
"渠道分组": "Channel grouping", "渠道分组": "Channel grouping",
...@@ -876,7 +878,7 @@ ...@@ -876,7 +878,7 @@
"启用模型限制(非必要,不建议启用)": "Enable model restrictions (not necessary, not recommended)", "启用模型限制(非必要,不建议启用)": "Enable model restrictions (not necessary, not recommended)",
"秒": "Second", "秒": "Second",
"更新令牌后需等待几分钟生效": "It will take a few minutes to take effect after updating the token.", "更新令牌后需等待几分钟生效": "It will take a few minutes to take effect after updating the token.",
"一小时": "one hour", "一小时": "One hour",
"新建数量": "New quantity", "新建数量": "New quantity",
"加载失败,请稍后重试": "Loading failed, please try again later", "加载失败,请稍后重试": "Loading failed, please try again later",
"未设置": "Not set", "未设置": "Not set",
......
...@@ -193,14 +193,16 @@ const EditChannel = (props) => { ...@@ -193,14 +193,16 @@ const EditChannel = (props) => {
const fetchUpstreamModelList = async (name) => { const fetchUpstreamModelList = async (name) => {
if (inputs['type'] !== 1) { // if (inputs['type'] !== 1) {
showError(t('仅支持 OpenAI 接口格式')); // showError(t('仅支持 OpenAI 接口格式'));
return; // return;
} // }
setLoading(true); setLoading(true);
const models = inputs['models'] || []; const models = inputs['models'] || [];
let err = false; let err = false;
if (isEdit) { if (isEdit) {
// 如果是编辑模式,使用已有的channel id获取模型列表
const res = await API.get('/api/channel/fetch_models/' + channelId); const res = await API.get('/api/channel/fetch_models/' + channelId);
if (res.data && res.data?.success) { if (res.data && res.data?.success) {
models.push(...res.data.data); models.push(...res.data.data);
...@@ -208,30 +210,29 @@ const EditChannel = (props) => { ...@@ -208,30 +210,29 @@ const EditChannel = (props) => {
err = true; err = true;
} }
} else { } else {
// 如果是新建模式,通过后端代理获取模型列表
if (!inputs?.['key']) { if (!inputs?.['key']) {
showError(t('请填写密钥')); showError(t('请填写密钥'));
err = true; err = true;
} else { } else {
try { try {
const host = new URL((inputs['base_url'] || 'https://api.openai.com')); const res = await API.post('/api/channel/fetch_models', {
base_url: inputs['base_url'],
const url = `https://${host.hostname}/v1/models`; key: inputs['key']
const key = inputs['key'];
const res = await axios.get(url, {
headers: {
'Authorization': `Bearer ${key}`
}
}); });
if (res.data) {
models.push(...res.data.data.map((model) => model.id)); if (res.data && res.data.success) {
models.push(...res.data.data);
} else { } else {
err = true; err = true;
} }
} catch (error) { } catch (error) {
console.error('Error fetching models:', error);
err = true; err = true;
} }
} }
} }
if (!err) { if (!err) {
handleInputChange(name, Array.from(new Set(models))); handleInputChange(name, Array.from(new Set(models)));
showSuccess(t('获取模型列表成功')); showSuccess(t('获取模型列表成功'));
...@@ -638,7 +639,7 @@ const EditChannel = (props) => { ...@@ -638,7 +639,7 @@ const EditChannel = (props) => {
{inputs.type === 21 && ( {inputs.type === 21 && (
<> <>
<div style={{ marginTop: 10 }}> <div style={{ marginTop: 10 }}>
<Typography.Text strong>识库 ID</Typography.Text> <Typography.Text strong>��识库 ID</Typography.Text>
</div> </div>
<Input <Input
label="知识库 ID" label="知识库 ID"
......
...@@ -23,6 +23,7 @@ import { ...@@ -23,6 +23,7 @@ import {
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import Title from '@douyinfe/semi-ui/lib/es/typography/title'; import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import { Divider } from 'semantic-ui-react'; import { Divider } from 'semantic-ui-react';
import { useTranslation } from 'react-i18next';
const EditToken = (props) => { const EditToken = (props) => {
const [isEdit, setIsEdit] = useState(false); const [isEdit, setIsEdit] = useState(false);
...@@ -52,6 +53,7 @@ const EditToken = (props) => { ...@@ -52,6 +53,7 @@ const EditToken = (props) => {
const [models, setModels] = useState([]); const [models, setModels] = useState([]);
const [groups, setGroups] = useState([]); const [groups, setGroups] = useState([]);
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation();
const handleInputChange = (name, value) => { const handleInputChange = (name, value) => {
setInputs((inputs) => ({ ...inputs, [name]: value })); setInputs((inputs) => ({ ...inputs, [name]: value }));
}; };
...@@ -87,7 +89,7 @@ const EditToken = (props) => { ...@@ -87,7 +89,7 @@ const EditToken = (props) => {
})); }));
setModels(localModelOptions); setModels(localModelOptions);
} else { } else {
showError(message); showError(t(message));
} }
}; };
...@@ -95,15 +97,13 @@ const EditToken = (props) => { ...@@ -95,15 +97,13 @@ const EditToken = (props) => {
let res = await API.get(`/api/user/self/groups`); let res = await API.get(`/api/user/self/groups`);
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
// return data is a map, key is group name, value is group description
// label is group description, value is group name
let localGroupOptions = Object.keys(data).map((group) => ({ let localGroupOptions = Object.keys(data).map((group) => ({
label: data[group], label: data[group],
value: group, value: group,
})); }));
setGroups(localGroupOptions); setGroups(localGroupOptions);
} else { } else {
showError(message); showError(t(message));
} }
}; };
...@@ -176,7 +176,7 @@ const EditToken = (props) => { ...@@ -176,7 +176,7 @@ const EditToken = (props) => {
if (localInputs.expired_time !== -1) { if (localInputs.expired_time !== -1) {
let time = Date.parse(localInputs.expired_time); let time = Date.parse(localInputs.expired_time);
if (isNaN(time)) { if (isNaN(time)) {
showError('过期时间格式错误!'); showError(t('过期时间格式错误!'));
setLoading(false); setLoading(false);
return; return;
} }
...@@ -189,11 +189,11 @@ const EditToken = (props) => { ...@@ -189,11 +189,11 @@ const EditToken = (props) => {
}); });
const { success, message } = res.data; const { success, message } = res.data;
if (success) { if (success) {
showSuccess('令牌更新成功!'); showSuccess(t('令牌更新成功!'));
props.refresh(); props.refresh();
props.handleClose(); props.handleClose();
} else { } else {
showError(message); showError(t(message));
} }
} else { } else {
// 处理新增多个令牌的情况 // 处理新增多个令牌的情况
...@@ -209,7 +209,7 @@ const EditToken = (props) => { ...@@ -209,7 +209,7 @@ const EditToken = (props) => {
if (localInputs.expired_time !== -1) { if (localInputs.expired_time !== -1) {
let time = Date.parse(localInputs.expired_time); let time = Date.parse(localInputs.expired_time);
if (isNaN(time)) { if (isNaN(time)) {
showError('过期时间格式错误!'); showError(t('过期时间格式错误!'));
setLoading(false); setLoading(false);
break; break;
} }
...@@ -222,14 +222,14 @@ const EditToken = (props) => { ...@@ -222,14 +222,14 @@ const EditToken = (props) => {
if (success) { if (success) {
successCount++; successCount++;
} else { } else {
showError(message); showError(t(message));
break; // 如果创建失败,终止循环 break; // 如果创建失败,终止循环
} }
} }
if (successCount > 0) { if (successCount > 0) {
showSuccess( showSuccess(
`${successCount}个令牌创建成功,请在列表页面点击复制获取令牌!`, t('令牌创建成功,请在列表页面点击复制获取令牌!')
); );
props.refresh(); props.refresh();
props.handleClose(); props.handleClose();
...@@ -245,7 +245,7 @@ const EditToken = (props) => { ...@@ -245,7 +245,7 @@ const EditToken = (props) => {
<SideSheet <SideSheet
placement={isEdit ? 'right' : 'left'} placement={isEdit ? 'right' : 'left'}
title={ title={
<Title level={3}>{isEdit ? '更新令牌信息' : '创建新的令牌'}</Title> <Title level={3}>{isEdit ? t('更新令牌信息') : t('创建新的令牌')}</Title>
} }
headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }} headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }} bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
...@@ -254,7 +254,7 @@ const EditToken = (props) => { ...@@ -254,7 +254,7 @@ const EditToken = (props) => {
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Space> <Space>
<Button theme='solid' size={'large'} onClick={submit}> <Button theme='solid' size={'large'} onClick={submit}>
提交 {t('提交')}
</Button> </Button>
<Button <Button
theme='solid' theme='solid'
...@@ -262,7 +262,7 @@ const EditToken = (props) => { ...@@ -262,7 +262,7 @@ const EditToken = (props) => {
type={'tertiary'} type={'tertiary'}
onClick={handleCancel} onClick={handleCancel}
> >
取消 {t('取消')}
</Button> </Button>
</Space> </Space>
</div> </div>
...@@ -274,9 +274,9 @@ const EditToken = (props) => { ...@@ -274,9 +274,9 @@ const EditToken = (props) => {
<Spin spinning={loading}> <Spin spinning={loading}>
<Input <Input
style={{ marginTop: 20 }} style={{ marginTop: 20 }}
label='名称' label={t('名称')}
name='name' name='name'
placeholder={'请输入名称'} placeholder={t('请输入名称')}
onChange={(value) => handleInputChange('name', value)} onChange={(value) => handleInputChange('name', value)}
value={name} value={name}
autoComplete='new-password' autoComplete='new-password'
...@@ -284,9 +284,9 @@ const EditToken = (props) => { ...@@ -284,9 +284,9 @@ const EditToken = (props) => {
/> />
<Divider /> <Divider />
<DatePicker <DatePicker
label='过期时间' label={t('过期时间')}
name='expired_time' name='expired_time'
placeholder={'请选择过期时间'} placeholder={t('请选择过期时间')}
onChange={(value) => handleInputChange('expired_time', value)} onChange={(value) => handleInputChange('expired_time', value)}
value={expired_time} value={expired_time}
autoComplete='new-password' autoComplete='new-password'
...@@ -300,7 +300,7 @@ const EditToken = (props) => { ...@@ -300,7 +300,7 @@ const EditToken = (props) => {
setExpiredTime(0, 0, 0, 0); setExpiredTime(0, 0, 0, 0);
}} }}
> >
永不过期 {t('永不过期')}
</Button> </Button>
<Button <Button
type={'tertiary'} type={'tertiary'}
...@@ -308,7 +308,7 @@ const EditToken = (props) => { ...@@ -308,7 +308,7 @@ const EditToken = (props) => {
setExpiredTime(0, 0, 1, 0); setExpiredTime(0, 0, 1, 0);
}} }}
> >
一小时 {t('一小时')}
</Button> </Button>
<Button <Button
type={'tertiary'} type={'tertiary'}
...@@ -316,7 +316,7 @@ const EditToken = (props) => { ...@@ -316,7 +316,7 @@ const EditToken = (props) => {
setExpiredTime(1, 0, 0, 0); setExpiredTime(1, 0, 0, 0);
}} }}
> >
一个月 {t('一个月')}
</Button> </Button>
<Button <Button
type={'tertiary'} type={'tertiary'}
...@@ -324,7 +324,7 @@ const EditToken = (props) => { ...@@ -324,7 +324,7 @@ const EditToken = (props) => {
setExpiredTime(0, 1, 0, 0); setExpiredTime(0, 1, 0, 0);
}} }}
> >
一天 {t('一天')}
</Button> </Button>
</Space> </Space>
</div> </div>
...@@ -332,17 +332,15 @@ const EditToken = (props) => { ...@@ -332,17 +332,15 @@ const EditToken = (props) => {
<Divider /> <Divider />
<Banner <Banner
type={'warning'} type={'warning'}
description={ description={t('注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。')}
'注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。'
}
></Banner> ></Banner>
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>{`额度${renderQuotaWithPrompt(remain_quota)}`}</Typography.Text> <Typography.Text>{`${t('额度')}${renderQuotaWithPrompt(remain_quota)}`}</Typography.Text>
</div> </div>
<AutoComplete <AutoComplete
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
name='remain_quota' name='remain_quota'
placeholder={'请输入额度'} placeholder={t('请输入额度')}
onChange={(value) => handleInputChange('remain_quota', value)} onChange={(value) => handleInputChange('remain_quota', value)}
value={remain_quota} value={remain_quota}
autoComplete='new-password' autoComplete='new-password'
...@@ -362,22 +360,22 @@ const EditToken = (props) => { ...@@ -362,22 +360,22 @@ const EditToken = (props) => {
{!isEdit && ( {!isEdit && (
<> <>
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>新建数量</Typography.Text> <Typography.Text>{t('新建数量')}</Typography.Text>
</div> </div>
<AutoComplete <AutoComplete
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
label='数量' label={t('数量')}
placeholder={'请选择或输入创建令牌的数量'} placeholder={t('请选择或输入创建令牌的数量')}
onChange={(value) => handleTokenCountChange(value)} onChange={(value) => handleTokenCountChange(value)}
onSelect={(value) => handleTokenCountChange(value)} onSelect={(value) => handleTokenCountChange(value)}
value={tokenCount.toString()} value={tokenCount.toString()}
autoComplete='off' autoComplete='off'
type='number' type='number'
data={[ data={[
{ value: 10, label: '10个' }, { value: 10, label: t('10个') },
{ value: 20, label: '20个' }, { value: 20, label: t('20个') },
{ value: 30, label: '30个' }, { value: 30, label: t('30个') },
{ value: 100, label: '100个' }, { value: 100, label: t('100个') },
]} ]}
disabled={unlimited_quota} disabled={unlimited_quota}
/> />
...@@ -392,17 +390,17 @@ const EditToken = (props) => { ...@@ -392,17 +390,17 @@ const EditToken = (props) => {
setUnlimitedQuota(); setUnlimitedQuota();
}} }}
> >
{unlimited_quota ? '取消无限额度' : '设为无限额度'} {unlimited_quota ? t('取消���限额度') : t('设为无限额度')}
</Button> </Button>
</div> </div>
<Divider /> <Divider />
<div style={{ marginTop: 10 }}> <div style={{ marginTop: 10 }}>
<Typography.Text>IP白名单(请勿过度信任此功能)</Typography.Text> <Typography.Text>{t('IP白名单(请勿过度信任此功能)')}</Typography.Text>
</div> </div>
<TextArea <TextArea
label='IP白名单' label={t('IP白名单')}
name='allow_ips' name='allow_ips'
placeholder={'允许的IP,一行一个'} placeholder={t('允许的IP,一行一个')}
onChange={(value) => { onChange={(value) => {
handleInputChange('allow_ips', value); handleInputChange('allow_ips', value);
}} }}
...@@ -417,16 +415,15 @@ const EditToken = (props) => { ...@@ -417,16 +415,15 @@ const EditToken = (props) => {
onChange={(e) => onChange={(e) =>
handleInputChange('model_limits_enabled', e.target.checked) handleInputChange('model_limits_enabled', e.target.checked)
} }
></Checkbox> >
<Typography.Text> {t('启用模型限制(非必要,不建议启用)')}
启用模型限制(非必要,不建议启用) </Checkbox>
</Typography.Text>
</Space> </Space>
</div> </div>
<Select <Select
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
placeholder={'请选择该渠道所支持的模型'} placeholder={t('请选择该渠道所支持的模型')}
name='models' name='models'
required required
multiple multiple
...@@ -440,12 +437,12 @@ const EditToken = (props) => { ...@@ -440,12 +437,12 @@ const EditToken = (props) => {
disabled={!model_limits_enabled} disabled={!model_limits_enabled}
/> />
<div style={{ marginTop: 10 }}> <div style={{ marginTop: 10 }}>
<Typography.Text>令牌分组,默认为用户的分组</Typography.Text> <Typography.Text>{t('令牌分组,默认为用户的分组')}</Typography.Text>
</div> </div>
{groups.length > 0 ? {groups.length > 0 ?
<Select <Select
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
placeholder={'令牌分组,默认为用户的分组'} placeholder={t('令牌分组,默认为用户的分组')}
name='gruop' name='gruop'
required required
selection selection
...@@ -458,7 +455,7 @@ const EditToken = (props) => { ...@@ -458,7 +455,7 @@ const EditToken = (props) => {
/>: />:
<Select <Select
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
placeholder={'管理员未设置用户可选分组'} placeholder={t('管理员未设置用户可选分组')}
name='gruop' name='gruop'
disabled={true} disabled={true}
/> />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment