feat: Integrate i18n support and enhance UI text localization
- Added internationalization (i18n) support across various components, enabling dynamic language switching and improved user experience. - Updated multiple components to utilize translation functions for labels, buttons, and messages, ensuring consistent language display. - Enhanced the user interface by refining text elements in the ChannelsTable, LogsTable, and various settings pages, improving clarity and accessibility. - Adjusted CSS styles for better responsiveness and layout consistency across different screen sizes.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
API,
|
||||
isMobile,
|
||||
@@ -61,6 +62,7 @@ function type2secretPrompt(type) {
|
||||
}
|
||||
|
||||
const EditChannel = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const channelId = props.editingChannel.id;
|
||||
const isEdit = channelId !== undefined;
|
||||
@@ -192,7 +194,7 @@ const EditChannel = (props) => {
|
||||
|
||||
const fetchUpstreamModelList = async (name) => {
|
||||
if (inputs['type'] !== 1) {
|
||||
showError('仅支持 OpenAI 接口格式');
|
||||
showError(t('仅支持 OpenAI 接口格式'));
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@@ -207,7 +209,7 @@ const EditChannel = (props) => {
|
||||
}
|
||||
} else {
|
||||
if (!inputs?.['key']) {
|
||||
showError('请填写密钥');
|
||||
showError(t('请填写密钥'));
|
||||
err = true;
|
||||
} else {
|
||||
try {
|
||||
@@ -232,9 +234,9 @@ const EditChannel = (props) => {
|
||||
}
|
||||
if (!err) {
|
||||
handleInputChange(name, Array.from(new Set(models)));
|
||||
showSuccess('获取模型列表成功');
|
||||
showSuccess(t('获取模型列表成功'));
|
||||
} else {
|
||||
showError('获取模型列表失败');
|
||||
showError(t('获取模型列表失败'));
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
@@ -305,15 +307,15 @@ const EditChannel = (props) => {
|
||||
|
||||
const submit = async () => {
|
||||
if (!isEdit && (inputs.name === '' || inputs.key === '')) {
|
||||
showInfo('请填写渠道名称和渠道密钥!');
|
||||
showInfo(t('请填写渠道名称和渠道密钥!'));
|
||||
return;
|
||||
}
|
||||
if (inputs.models.length === 0) {
|
||||
showInfo('请至少选择一个模型!');
|
||||
showInfo(t('请至少选择一个模型!'));
|
||||
return;
|
||||
}
|
||||
if (inputs.model_mapping !== '' && !verifyJSON(inputs.model_mapping)) {
|
||||
showInfo('模型映射必须是合法的 JSON 格式!');
|
||||
showInfo(t('模型映射必须是合法的 JSON 格式!'));
|
||||
return;
|
||||
}
|
||||
let localInputs = { ...inputs };
|
||||
@@ -331,7 +333,7 @@ const EditChannel = (props) => {
|
||||
}
|
||||
let res;
|
||||
if (!Array.isArray(localInputs.models)) {
|
||||
showError('提交失败,请勿重复提交!');
|
||||
showError(t('提交失败,请勿重复提交!'));
|
||||
handleCancel();
|
||||
return;
|
||||
}
|
||||
@@ -349,9 +351,9 @@ const EditChannel = (props) => {
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
if (isEdit) {
|
||||
showSuccess('渠道更新成功!');
|
||||
showSuccess(t('渠道更新成功!'));
|
||||
} else {
|
||||
showSuccess('渠道创建成功!');
|
||||
showSuccess(t('渠道创建成功!'));
|
||||
setInputs(originInputs);
|
||||
}
|
||||
props.refresh();
|
||||
@@ -363,7 +365,6 @@ const EditChannel = (props) => {
|
||||
|
||||
const addCustomModels = () => {
|
||||
if (customModel.trim() === '') return;
|
||||
// 使用逗号分隔字符串,然后去除每个模型名称前后的空格
|
||||
const modelArray = customModel.split(',').map((model) => model.trim());
|
||||
|
||||
let localModels = [...inputs.models];
|
||||
@@ -371,24 +372,21 @@ const EditChannel = (props) => {
|
||||
let hasError = false;
|
||||
|
||||
modelArray.forEach((model) => {
|
||||
// 检查模型是否已存在,且模型名称非空
|
||||
if (model && !localModels.includes(model)) {
|
||||
localModels.push(model); // 添加到模型列表
|
||||
localModels.push(model);
|
||||
localModelOptions.push({
|
||||
// 添加到下拉选项
|
||||
key: model,
|
||||
text: model,
|
||||
value: model
|
||||
});
|
||||
} else if (model) {
|
||||
showError('某些模型已存在!');
|
||||
showError(t('某些模型已存在!'));
|
||||
hasError = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasError) return; // 如果有错误则终止操作
|
||||
if (hasError) return;
|
||||
|
||||
// 更新状态值
|
||||
setModelOptions(localModelOptions);
|
||||
setCustomModel('');
|
||||
handleInputChange('models', localModels);
|
||||
@@ -401,7 +399,7 @@ const EditChannel = (props) => {
|
||||
maskClosable={false}
|
||||
placement={isEdit ? 'right' : 'left'}
|
||||
title={
|
||||
<Title level={3}>{isEdit ? '更新渠道信息' : '创建新的渠道'}</Title>
|
||||
<Title level={3}>{isEdit ? t('更新渠道信息') : t('创建新的渠道')}</Title>
|
||||
}
|
||||
headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
|
||||
bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
|
||||
@@ -410,7 +408,7 @@ const EditChannel = (props) => {
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Space>
|
||||
<Button theme="solid" size={'large'} onClick={submit}>
|
||||
提交
|
||||
{t('提交')}
|
||||
</Button>
|
||||
<Button
|
||||
theme="solid"
|
||||
@@ -418,7 +416,7 @@ const EditChannel = (props) => {
|
||||
type={'tertiary'}
|
||||
onClick={handleCancel}
|
||||
>
|
||||
取消
|
||||
{t('取消')}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
@@ -429,7 +427,7 @@ const EditChannel = (props) => {
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>类型:</Typography.Text>
|
||||
<Typography.Text strong>{t('类型')}:</Typography.Text>
|
||||
</div>
|
||||
<Select
|
||||
name="type"
|
||||
@@ -444,20 +442,7 @@ const EditChannel = (props) => {
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Banner
|
||||
type={'warning'}
|
||||
description={
|
||||
<>
|
||||
注意,<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>
|
||||
。
|
||||
</>
|
||||
}
|
||||
description={t('注意,模型部署名称必须和模型名称保持一致,因为 One API 会把请求体中的 model 参数替换为你的部署名称(模型名称中的点会被剔除)')}
|
||||
></Banner>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
@@ -468,9 +453,7 @@ const EditChannel = (props) => {
|
||||
<Input
|
||||
label="AZURE_OPENAI_ENDPOINT"
|
||||
name="azure_base_url"
|
||||
placeholder={
|
||||
'请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com'
|
||||
}
|
||||
placeholder={t('请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
@@ -478,14 +461,12 @@ const EditChannel = (props) => {
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>默认 API 版本:</Typography.Text>
|
||||
<Typography.Text strong>{t('默认 API 版本')}:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label="默认 API 版本"
|
||||
label={t('默认 API 版本')}
|
||||
name="azure_other"
|
||||
placeholder={
|
||||
'请输入默认 API 版本,例如:2023-06-01-preview,该配置可以被实际的请求查询参数所覆盖'
|
||||
}
|
||||
placeholder={t('请输入默认 API 版本,例如:2023-06-01-preview,该配置可以被实际的请求查询参数所覆盖')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('other', value);
|
||||
}}
|
||||
@@ -499,23 +480,17 @@ const EditChannel = (props) => {
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Banner
|
||||
type={'warning'}
|
||||
description={
|
||||
<>
|
||||
如果你对接的是上游One API或者New API等转发项目,请使用OpenAI类型,不要使用此类型,除非你知道你在做什么。
|
||||
</>
|
||||
}
|
||||
description={t('如果你对接的是上游One API或者New API等转发项目,请使用OpenAI类型,不要使用此类型,除非你知道你在做什么。')}
|
||||
></Banner>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
完整的 Base URL,支持变量{'{model}'}:
|
||||
{t('完整的 Base URL,支持变量{model}')}:
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="base_url"
|
||||
placeholder={
|
||||
'请输入完整的URL,例如:https://api.openai.com/v1/chat/completions'
|
||||
}
|
||||
placeholder={t('请输入完整的URL,例如:https://api.openai.com/v1/chat/completions')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
@@ -527,12 +502,12 @@ const EditChannel = (props) => {
|
||||
{inputs.type !== 3 && inputs.type !== 8 && inputs.type !== 22 && inputs.type !== 36 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>代理:</Typography.Text>
|
||||
<Typography.Text strong>{t('代理')}:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label="代理"
|
||||
label={t('代理')}
|
||||
name="base_url"
|
||||
placeholder={'此项可选,用于通过代理站来进行 API 调用'}
|
||||
placeholder={t('此项可选,用于通过代理站来进行 API 调用')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
@@ -544,13 +519,11 @@ const EditChannel = (props) => {
|
||||
{inputs.type === 22 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>私有部署地址:</Typography.Text>
|
||||
<Typography.Text strong>{t('私有部署地址')}:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="base_url"
|
||||
placeholder={
|
||||
'请输入私有部署地址,格式为:https://fastgpt.run/api/openapi'
|
||||
}
|
||||
placeholder={t('请输入私有部署地址,格式为:https://fastgpt.run/api/openapi')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
@@ -563,14 +536,12 @@ const EditChannel = (props) => {
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
注意非Chat API,请务必填写正确的API地址,否则可能导致无法使用
|
||||
{t('注意非Chat API,请务必填写正确的API地址,否则可能导致无法使用')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="base_url"
|
||||
placeholder={
|
||||
'请输入到 /suno 前的路径,通常就是域名,例如:https://api.example.com '
|
||||
}
|
||||
placeholder={t('请输入到 /suno 前的路径,通常就是域名,例如:https://api.example.com')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
@@ -580,12 +551,12 @@ const EditChannel = (props) => {
|
||||
</>
|
||||
)}
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>名称:</Typography.Text>
|
||||
<Typography.Text strong>{t('名称')}:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
required
|
||||
name="name"
|
||||
placeholder={'请为渠道命名'}
|
||||
placeholder={t('请为渠道命名')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('name', value);
|
||||
}}
|
||||
@@ -593,16 +564,16 @@ const EditChannel = (props) => {
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>分组:</Typography.Text>
|
||||
<Typography.Text strong>{t('分组')}:</Typography.Text>
|
||||
</div>
|
||||
<Select
|
||||
placeholder={'请选择可以使用该渠道的分组'}
|
||||
placeholder={t('请选择可以使用该渠道的分组')}
|
||||
name="groups"
|
||||
required
|
||||
multiple
|
||||
selection
|
||||
allowAdditions
|
||||
additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
|
||||
additionLabel={t('请在系统设置页面编辑分组倍率以添加新的分组:')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('groups', value);
|
||||
}}
|
||||
@@ -631,17 +602,15 @@ const EditChannel = (props) => {
|
||||
{inputs.type === 41 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>部署地区:</Typography.Text>
|
||||
<Typography.Text strong>{t('部署地区')}:</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
name="other"
|
||||
placeholder={
|
||||
'请输入部署地区,例如:us-central1\n支持使用模型映射格式\n' +
|
||||
placeholder={t('请输入部署地区,例如:us-central1\n支持使用模型映射格式\n' +
|
||||
'{\n' +
|
||||
' "default": "us-central1",\n' +
|
||||
' "claude-3-5-sonnet-20240620": "europe-west1"\n' +
|
||||
'}'
|
||||
}
|
||||
'}')}
|
||||
autosize={{ minRows: 2 }}
|
||||
onChange={(value) => {
|
||||
handleInputChange('other', value);
|
||||
@@ -662,7 +631,7 @@ const EditChannel = (props) => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
填入模板
|
||||
{t('填入模板')}
|
||||
</Typography.Text>
|
||||
</>
|
||||
)}
|
||||
@@ -702,7 +671,7 @@ const EditChannel = (props) => {
|
||||
</>
|
||||
)}
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>模型:</Typography.Text>
|
||||
<Typography.Text strong>{t('模型')}:</Typography.Text>
|
||||
</div>
|
||||
<Select
|
||||
placeholder={'请选择该渠道所支持的模型'}
|
||||
@@ -727,7 +696,7 @@ const EditChannel = (props) => {
|
||||
handleInputChange('models', basicModels);
|
||||
}}
|
||||
>
|
||||
填入相关模型
|
||||
{t('填入相关模型')}
|
||||
</Button>
|
||||
<Button
|
||||
type="secondary"
|
||||
@@ -735,16 +704,16 @@ const EditChannel = (props) => {
|
||||
handleInputChange('models', fullModels);
|
||||
}}
|
||||
>
|
||||
填入所有模型
|
||||
{t('填入所有模型')}
|
||||
</Button>
|
||||
<Tooltip content={fetchButtonTips}>
|
||||
<Tooltip content={t('新建渠道时,请求通过当前浏览器发出;编辑已有渠道,请求通过后端服务器发出')}>
|
||||
<Button
|
||||
type="tertiary"
|
||||
onClick={() => {
|
||||
fetchUpstreamModelList('models');
|
||||
}}
|
||||
>
|
||||
获取模型列表
|
||||
{t('获取模型列表')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
@@ -753,16 +722,16 @@ const EditChannel = (props) => {
|
||||
handleInputChange('models', []);
|
||||
}}
|
||||
>
|
||||
清除所有模型
|
||||
{t('清除所有模型')}
|
||||
</Button>
|
||||
</Space>
|
||||
<Input
|
||||
addonAfter={
|
||||
<Button type="primary" onClick={addCustomModels}>
|
||||
填入
|
||||
{t('填入')}
|
||||
</Button>
|
||||
}
|
||||
placeholder="输入自定义模型名称"
|
||||
placeholder={t('输入自定义模型名称')}
|
||||
value={customModel}
|
||||
onChange={(value) => {
|
||||
setCustomModel(value.trim());
|
||||
@@ -770,10 +739,10 @@ const EditChannel = (props) => {
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>模型重定向:</Typography.Text>
|
||||
<Typography.Text strong>{t('模型重定向')}:</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={`此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`}
|
||||
placeholder={t('此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:') + `\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`}
|
||||
name="model_mapping"
|
||||
onChange={(value) => {
|
||||
handleInputChange('model_mapping', value);
|
||||
@@ -795,17 +764,17 @@ const EditChannel = (props) => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
填入模板
|
||||
{t('填入模板')}
|
||||
</Typography.Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>密钥:</Typography.Text>
|
||||
<Typography.Text strong>{t('密钥')}:</Typography.Text>
|
||||
</div>
|
||||
{batch ? (
|
||||
<TextArea
|
||||
label="密钥"
|
||||
label={t('密钥')}
|
||||
name="key"
|
||||
required
|
||||
placeholder={'请输入密钥,一行一个'}
|
||||
placeholder={t('请输入密钥,一行一个')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('key', value);
|
||||
}}
|
||||
@@ -817,7 +786,7 @@ const EditChannel = (props) => {
|
||||
<>
|
||||
{inputs.type === 41 ? (
|
||||
<TextArea
|
||||
label="鉴权json"
|
||||
label={t('鉴权json')}
|
||||
name="key"
|
||||
required
|
||||
placeholder={'{\n' +
|
||||
@@ -842,18 +811,17 @@ const EditChannel = (props) => {
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
label="密钥"
|
||||
label={t('密钥')}
|
||||
name="key"
|
||||
required
|
||||
placeholder={type2secretPrompt(inputs.type)}
|
||||
placeholder={t(type2secretPrompt(inputs.type))}
|
||||
onChange={(value) => {
|
||||
handleInputChange('key', value);
|
||||
}}
|
||||
value={inputs.key}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isEdit && (
|
||||
@@ -861,23 +829,23 @@ const EditChannel = (props) => {
|
||||
<Space>
|
||||
<Checkbox
|
||||
checked={batch}
|
||||
label="批量创建"
|
||||
label={t('批量创建')}
|
||||
name="batch"
|
||||
onChange={() => setBatch(!batch)}
|
||||
/>
|
||||
<Typography.Text strong>批量创建</Typography.Text>
|
||||
<Typography.Text strong>{t('批量创建')}</Typography.Text>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
{inputs.type === 1 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>组织:</Typography.Text>
|
||||
<Typography.Text strong>{t('组织')}:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label="组织,可选,不填则为默认组织"
|
||||
label={t('组织,可选,不填则为默认组织')}
|
||||
name="openai_organization"
|
||||
placeholder="请输入组织org-xxx"
|
||||
placeholder={t('请输入组织org-xxx')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('openai_organization', value);
|
||||
}}
|
||||
@@ -886,11 +854,11 @@ const EditChannel = (props) => {
|
||||
</>
|
||||
)}
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>默认测试模型:</Typography.Text>
|
||||
<Typography.Text strong>{t('默认测试模型')}:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="test_model"
|
||||
placeholder="不填则为模型列表第一个"
|
||||
placeholder={t('不填则为模型列表第一个')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('test_model', value);
|
||||
}}
|
||||
@@ -904,20 +872,20 @@ const EditChannel = (props) => {
|
||||
onChange={() => {
|
||||
setAutoBan(!autoBan);
|
||||
}}
|
||||
// onChange={handleInputChange}
|
||||
/>
|
||||
<Typography.Text strong>
|
||||
是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道:
|
||||
{t('是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道:')}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
状态码复写(仅影响本地判断,不修改返回到上游的状态码):
|
||||
{t('状态码复写(仅影响本地判断,不修改返回到上游的状态码)')}:
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={`此项可选,用于复写返回的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:\n${JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)}`}
|
||||
placeholder={t('此项可选,用于复写返回的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:') +
|
||||
'\n' + JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)}
|
||||
name="status_code_mapping"
|
||||
onChange={(value) => {
|
||||
handleInputChange('status_code_mapping', value);
|
||||
@@ -939,17 +907,17 @@ const EditChannel = (props) => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
填入模板
|
||||
{t('填入模板')}
|
||||
</Typography.Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
渠道标签
|
||||
{t('渠道标签')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label="渠道标签"
|
||||
label={t('渠道标签')}
|
||||
name="tag"
|
||||
placeholder={'渠道标签'}
|
||||
placeholder={t('渠道标签')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('tag', value);
|
||||
}}
|
||||
@@ -958,13 +926,13 @@ const EditChannel = (props) => {
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
渠道优先级
|
||||
{t('渠道优先级')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label="渠道优先级"
|
||||
label={t('渠道优先级')}
|
||||
name="priority"
|
||||
placeholder={'渠道优先级'}
|
||||
placeholder={t('渠道优先级')}
|
||||
onChange={(value) => {
|
||||
const number = parseInt(value);
|
||||
if (isNaN(number)) {
|
||||
@@ -978,13 +946,13 @@ const EditChannel = (props) => {
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
渠道权重
|
||||
{t('渠道权重')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label="渠道权重"
|
||||
label={t('渠道权重')}
|
||||
name="weight"
|
||||
placeholder={'渠道权重'}
|
||||
placeholder={t('渠道权重')}
|
||||
onChange={(value) => {
|
||||
const number = parseInt(value);
|
||||
if (isNaN(number)) {
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import React from 'react';
|
||||
import ChannelsTable from '../../components/ChannelsTable';
|
||||
import { Layout } from '@douyinfe/semi-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const File = () => (
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<h3>管理渠道</h3>
|
||||
const File = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<h3>{t('管理渠道')}</h3>
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<ChannelsTable />
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default File;
|
||||
|
||||
@@ -21,8 +21,10 @@ import {
|
||||
} from '../../helpers/render';
|
||||
import { UserContext } from '../../context/User/index.js';
|
||||
import { StyleContext } from '../../context/Style/index.js';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const Detail = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const formRef = useRef();
|
||||
let now = new Date();
|
||||
const [userState, userDispatch] = useContext(UserContext);
|
||||
@@ -85,8 +87,8 @@ const Detail = (props) => {
|
||||
},
|
||||
title: {
|
||||
visible: true,
|
||||
text: '模型调用次数占比',
|
||||
subtext: `总计:${renderNumber(times)}`,
|
||||
text: t('模型调用次数占比'),
|
||||
subtext: `${t('总计')}:${renderNumber(times)}`,
|
||||
},
|
||||
legends: {
|
||||
visible: true,
|
||||
@@ -125,11 +127,10 @@ const Detail = (props) => {
|
||||
},
|
||||
title: {
|
||||
visible: true,
|
||||
text: '模型消耗分布',
|
||||
subtext: `总计:${renderQuota(consumeQuota, 2)}`,
|
||||
text: t('模型消耗分布'),
|
||||
subtext: `${t('总计')}:${renderQuota(consumeQuota, 2)}`,
|
||||
},
|
||||
bar: {
|
||||
// The state style of bar
|
||||
state: {
|
||||
hover: {
|
||||
stroke: '#000',
|
||||
@@ -155,9 +156,7 @@ const Detail = (props) => {
|
||||
},
|
||||
],
|
||||
updateContent: (array) => {
|
||||
// sort by value
|
||||
array.sort((a, b) => b.value - a.value);
|
||||
// add $
|
||||
let sum = 0;
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
sum += parseFloat(array[i].value);
|
||||
@@ -166,9 +165,8 @@ const Detail = (props) => {
|
||||
4,
|
||||
);
|
||||
}
|
||||
// add to first
|
||||
array.unshift({
|
||||
key: '总计',
|
||||
key: t('总计'),
|
||||
value: renderQuotaNumberWithDigit(sum, 4),
|
||||
});
|
||||
return array;
|
||||
@@ -331,7 +329,7 @@ const Detail = (props) => {
|
||||
data: [{ id: 'id0', values: newPieData }],
|
||||
title: {
|
||||
...prev.title,
|
||||
subtext: `总计:${renderNumber(totalTimes)}`
|
||||
subtext: `${t('总计')}:${renderNumber(totalTimes)}`
|
||||
},
|
||||
color: {
|
||||
specified: newModelColors
|
||||
@@ -343,7 +341,7 @@ const Detail = (props) => {
|
||||
data: [{ id: 'barData', values: newLineData }],
|
||||
title: {
|
||||
...prev.title,
|
||||
subtext: `总计:${renderQuota(totalQuota, 2)}`
|
||||
subtext: `${t('总计')}:${renderQuota(totalQuota, 2)}`
|
||||
},
|
||||
color: {
|
||||
specified: newModelColors
|
||||
@@ -382,14 +380,14 @@ const Detail = (props) => {
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<h3>数据看板</h3>
|
||||
<h3>{t('数据看板')}</h3>
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<Form ref={formRef} layout='horizontal' style={{ marginTop: 10 }}>
|
||||
<>
|
||||
<Form.DatePicker
|
||||
field='start_timestamp'
|
||||
label='起始时间'
|
||||
label={t('起始时间')}
|
||||
style={{ width: 272 }}
|
||||
initValue={start_timestamp}
|
||||
value={start_timestamp}
|
||||
@@ -402,7 +400,7 @@ const Detail = (props) => {
|
||||
<Form.DatePicker
|
||||
field='end_timestamp'
|
||||
fluid
|
||||
label='结束时间'
|
||||
label={t('结束时间')}
|
||||
style={{ width: 272 }}
|
||||
initValue={end_timestamp}
|
||||
value={end_timestamp}
|
||||
@@ -412,15 +410,15 @@ const Detail = (props) => {
|
||||
/>
|
||||
<Form.Select
|
||||
field='data_export_default_time'
|
||||
label='时间粒度'
|
||||
label={t('时间粒度')}
|
||||
style={{ width: 176 }}
|
||||
initValue={dataExportDefaultTime}
|
||||
placeholder={'时间粒度'}
|
||||
placeholder={t('时间粒度')}
|
||||
name='data_export_default_time'
|
||||
optionList={[
|
||||
{ label: '小时', value: 'hour' },
|
||||
{ label: '天', value: 'day' },
|
||||
{ label: '周', value: 'week' },
|
||||
{ label: t('小时'), value: 'hour' },
|
||||
{ label: t('天'), value: 'day' },
|
||||
{ label: t('周'), value: 'week' },
|
||||
]}
|
||||
onChange={(value) =>
|
||||
handleInputChange(value, 'data_export_default_time')
|
||||
@@ -430,17 +428,17 @@ const Detail = (props) => {
|
||||
<>
|
||||
<Form.Input
|
||||
field='username'
|
||||
label='用户名称'
|
||||
label={t('用户名称')}
|
||||
style={{ width: 176 }}
|
||||
value={username}
|
||||
placeholder={'可选值'}
|
||||
placeholder={t('可选值')}
|
||||
name='username'
|
||||
onChange={(value) => handleInputChange(value, 'username')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
label='查询'
|
||||
label={t('查询')}
|
||||
type='primary'
|
||||
htmlType='submit'
|
||||
className='btn-margin-right'
|
||||
@@ -448,7 +446,7 @@ const Detail = (props) => {
|
||||
loading={loading}
|
||||
style={{ marginTop: 24 }}
|
||||
>
|
||||
查询
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<Form.Section>
|
||||
</Form.Section>
|
||||
@@ -459,13 +457,13 @@ const Detail = (props) => {
|
||||
<Col span={styleState.isMobile?24:8}>
|
||||
<Card className='panel-desc-card'>
|
||||
<Descriptions row size="small">
|
||||
<Descriptions.Item itemKey='当前余额'>
|
||||
<Descriptions.Item itemKey={t('当前余额')}>
|
||||
{renderQuota(userState?.user?.quota)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item itemKey='历史消耗'>
|
||||
<Descriptions.Item itemKey={t('历史消耗')}>
|
||||
{renderQuota(userState?.user?.used_quota)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item itemKey='请求次数'>
|
||||
<Descriptions.Item itemKey={t('请求次数')}>
|
||||
{userState.user?.request_count}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
@@ -474,13 +472,13 @@ const Detail = (props) => {
|
||||
<Col span={styleState.isMobile?24:8}>
|
||||
<Card>
|
||||
<Descriptions row size="small">
|
||||
<Descriptions.Item itemKey='统计额度'>
|
||||
<Descriptions.Item itemKey={t('统计额度')}>
|
||||
{renderQuota(consumeQuota)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item itemKey='统计Tokens'>
|
||||
<Descriptions.Item itemKey={t('统计Tokens')}>
|
||||
{consumeTokens}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item itemKey='统计次数'>
|
||||
<Descriptions.Item itemKey={t('统计次数')}>
|
||||
{times}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
@@ -489,13 +487,13 @@ const Detail = (props) => {
|
||||
<Col span={styleState.isMobile ? 24 : 8}>
|
||||
<Card>
|
||||
<Descriptions row size='small'>
|
||||
<Descriptions.Item itemKey='平均RPM'>
|
||||
<Descriptions.Item itemKey={t('平均RPM')}>
|
||||
{(times /
|
||||
((Date.parse(end_timestamp) -
|
||||
Date.parse(start_timestamp)) /
|
||||
60000)).toFixed(3)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item itemKey='平均TPM'>
|
||||
<Descriptions.Item itemKey={t('平均TPM')}>
|
||||
{(consumeTokens /
|
||||
((Date.parse(end_timestamp) -
|
||||
Date.parse(start_timestamp)) /
|
||||
@@ -507,7 +505,7 @@ const Detail = (props) => {
|
||||
</Row>
|
||||
<Card style={{marginTop: 20}}>
|
||||
<Tabs type="line" defaultActiveKey="1">
|
||||
<Tabs.TabPane tab="消耗分布" itemKey="1">
|
||||
<Tabs.TabPane tab={t('消耗分布')} itemKey="1">
|
||||
<div style={{ height: 500 }}>
|
||||
<VChart
|
||||
spec={spec_line}
|
||||
@@ -515,7 +513,7 @@ const Detail = (props) => {
|
||||
/>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="调用次数分布" itemKey="2">
|
||||
<Tabs.TabPane tab={t('调用次数分布')} itemKey="2">
|
||||
<div style={{ height: 500 }}>
|
||||
<VChart
|
||||
spec={spec_pie}
|
||||
|
||||
@@ -6,24 +6,10 @@ import { Card, Chat, Input, Layout, Select, Slider, TextArea, Typography, Button
|
||||
import { SSE } from 'sse';
|
||||
import { IconSetting } from '@douyinfe/semi-icons';
|
||||
import { StyleContext } from '../../context/Style/index.js';
|
||||
|
||||
const defaultMessage = [
|
||||
{
|
||||
role: 'user',
|
||||
id: '2',
|
||||
createAt: 1715676751919,
|
||||
content: "你好",
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
id: '3',
|
||||
createAt: 1715676751919,
|
||||
content: "你好,请问有什么可以帮助您的吗?",
|
||||
}
|
||||
];
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const roleInfo = {
|
||||
user: {
|
||||
user: {
|
||||
name: 'User',
|
||||
avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png'
|
||||
},
|
||||
@@ -43,6 +29,23 @@ function getId() {
|
||||
}
|
||||
|
||||
const Playground = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const defaultMessage = [
|
||||
{
|
||||
role: 'user',
|
||||
id: '2',
|
||||
createAt: 1715676751919,
|
||||
content: t('你好'),
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
id: '3',
|
||||
createAt: 1715676751919,
|
||||
content: t('你好,请问有什么可以帮助您的吗?'),
|
||||
}
|
||||
];
|
||||
|
||||
const [inputs, setInputs] = useState({
|
||||
model: 'gpt-4o-mini',
|
||||
group: '',
|
||||
@@ -65,7 +68,7 @@ const Playground = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get('expired')) {
|
||||
showError('未登录或登录已过期,请重新登录!');
|
||||
showError(t('未登录或登录已过期,请重新登录!'));
|
||||
}
|
||||
let status = localStorage.getItem('status');
|
||||
if (status) {
|
||||
@@ -86,7 +89,7 @@ const Playground = () => {
|
||||
}));
|
||||
setModels(localModelOptions);
|
||||
} else {
|
||||
showError(message);
|
||||
showError(t(message));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -115,7 +118,7 @@ const Playground = () => {
|
||||
}
|
||||
} else {
|
||||
localGroupOptions = [{
|
||||
label: '用户分组',
|
||||
label: t('用户分组'),
|
||||
value: '',
|
||||
}];
|
||||
setGroups(localGroupOptions);
|
||||
@@ -123,7 +126,7 @@ const Playground = () => {
|
||||
setGroups(localGroupOptions);
|
||||
handleInputChange('group', localGroupOptions[0].value);
|
||||
} else {
|
||||
showError(message);
|
||||
showError(t(message));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -314,10 +317,10 @@ const Playground = () => {
|
||||
<Layout.Sider style={{ display: styleState.isMobile ? 'block' : 'initial' }}>
|
||||
<Card style={commonOuterStyle}>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>分组:</Typography.Text>
|
||||
<Typography.Text strong>{t('分组')}:</Typography.Text>
|
||||
</div>
|
||||
<Select
|
||||
placeholder={'请选择分组'}
|
||||
placeholder={t('请选择分组')}
|
||||
name='group'
|
||||
required
|
||||
selection
|
||||
@@ -334,10 +337,10 @@ const Playground = () => {
|
||||
}))}
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>模型:</Typography.Text>
|
||||
<Typography.Text strong>{t('模型')}:</Typography.Text>
|
||||
</div>
|
||||
<Select
|
||||
placeholder={'请选择模型'}
|
||||
placeholder={t('请选择模型')}
|
||||
name='model'
|
||||
required
|
||||
selection
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
API,
|
||||
downloadTextAsFile,
|
||||
@@ -22,6 +23,7 @@ import Title from '@douyinfe/semi-ui/lib/es/typography/title';
|
||||
import { Divider } from 'semantic-ui-react';
|
||||
|
||||
const EditRedemption = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const isEdit = props.editingRedemption.id !== undefined;
|
||||
const [loading, setLoading] = useState(isEdit);
|
||||
|
||||
@@ -69,7 +71,7 @@ const EditRedemption = (props) => {
|
||||
let name = inputs.name;
|
||||
if (!isEdit && inputs.name === '') {
|
||||
// set default name
|
||||
name = '兑换码-' + renderQuota(quota);
|
||||
name = t('新建兑换码') + ' ' + renderQuota(quota);
|
||||
}
|
||||
setLoading(true);
|
||||
let localInputs = inputs;
|
||||
@@ -90,11 +92,11 @@ const EditRedemption = (props) => {
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (isEdit) {
|
||||
showSuccess('兑换码更新成功!');
|
||||
showSuccess(t('兑换码更新成功!'));
|
||||
props.refresh();
|
||||
props.handleClose();
|
||||
} else {
|
||||
showSuccess('兑换码创建成功!');
|
||||
showSuccess(t('兑换码创建成功!'));
|
||||
setInputs(originInputs);
|
||||
props.refresh();
|
||||
props.handleClose();
|
||||
@@ -107,13 +109,12 @@ const EditRedemption = (props) => {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
text += data[i] + '\n';
|
||||
}
|
||||
// downloadTextAsFile(text, `${inputs.name}.txt`);
|
||||
Modal.confirm({
|
||||
title: '兑换码创建成功',
|
||||
title: t('兑换码创建成功'),
|
||||
content: (
|
||||
<div>
|
||||
<p>兑换码创建成功,是否下载兑换码?</p>
|
||||
<p>兑换码将以文本文件的形式下载,文件名为兑换码的名称。</p>
|
||||
<p>{t('兑换码创建成功,是否下载兑换码?')}</p>
|
||||
<p>{t('兑换码将以文本文件的形式下载,文件名为兑换码的名称。')}</p>
|
||||
</div>
|
||||
),
|
||||
onOk: () => {
|
||||
@@ -130,7 +131,7 @@ const EditRedemption = (props) => {
|
||||
placement={isEdit ? 'right' : 'left'}
|
||||
title={
|
||||
<Title level={3}>
|
||||
{isEdit ? '更新兑换码信息' : '创建新的兑换码'}
|
||||
{isEdit ? t('更新兑换码信息') : t('创建新的兑换码')}
|
||||
</Title>
|
||||
}
|
||||
headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
|
||||
@@ -140,7 +141,7 @@ const EditRedemption = (props) => {
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Space>
|
||||
<Button theme='solid' size={'large'} onClick={submit}>
|
||||
提交
|
||||
{t('提交')}
|
||||
</Button>
|
||||
<Button
|
||||
theme='solid'
|
||||
@@ -148,7 +149,7 @@ const EditRedemption = (props) => {
|
||||
type={'tertiary'}
|
||||
onClick={handleCancel}
|
||||
>
|
||||
取消
|
||||
{t('取消')}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
@@ -160,9 +161,9 @@ const EditRedemption = (props) => {
|
||||
<Spin spinning={loading}>
|
||||
<Input
|
||||
style={{ marginTop: 20 }}
|
||||
label='名称'
|
||||
label={t('名称')}
|
||||
name='name'
|
||||
placeholder={'请输入名称'}
|
||||
placeholder={t('请输入名称')}
|
||||
onChange={(value) => handleInputChange('name', value)}
|
||||
value={name}
|
||||
autoComplete='new-password'
|
||||
@@ -170,12 +171,12 @@ const EditRedemption = (props) => {
|
||||
/>
|
||||
<Divider />
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>{`额度${renderQuotaWithPrompt(quota)}`}</Typography.Text>
|
||||
<Typography.Text>{t('额度') + renderQuotaWithPrompt(quota)}</Typography.Text>
|
||||
</div>
|
||||
<AutoComplete
|
||||
style={{ marginTop: 8 }}
|
||||
name='quota'
|
||||
placeholder={'请输入额度'}
|
||||
placeholder={t('请输入额度')}
|
||||
onChange={(value) => handleInputChange('quota', value)}
|
||||
value={quota}
|
||||
autoComplete='new-password'
|
||||
@@ -193,12 +194,12 @@ const EditRedemption = (props) => {
|
||||
{!isEdit && (
|
||||
<>
|
||||
<Divider />
|
||||
<Typography.Text>生成数量</Typography.Text>
|
||||
<Typography.Text>{t('生成数量')}</Typography.Text>
|
||||
<Input
|
||||
style={{ marginTop: 8 }}
|
||||
label='生成数量'
|
||||
label={t('生成数量')}
|
||||
name='count'
|
||||
placeholder={'请输入生成数量'}
|
||||
placeholder={t('请输入生成数量')}
|
||||
onChange={(value) => handleInputChange('count', value)}
|
||||
value={count}
|
||||
autoComplete='new-password'
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import RedemptionsTable from '../../components/RedemptionsTable';
|
||||
import { Layout } from '@douyinfe/semi-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const Redemption = () => (
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<h3>管理兑换码</h3>
|
||||
const Redemption = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<h3>{t('管理兑换码')}</h3>
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<RedemptionsTable />
|
||||
@@ -14,5 +17,6 @@ const Redemption = () => (
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Redemption;
|
||||
|
||||
@@ -9,8 +9,10 @@ import {
|
||||
verifyJSON,
|
||||
verifyJSONPromise
|
||||
} from '../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SettingsChats(props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [inputs, setInputs] = useState({
|
||||
Chats: "[]",
|
||||
@@ -24,7 +26,7 @@ export default function SettingsChats(props) {
|
||||
await refForm.current.validate().then(() => {
|
||||
console.log('Validation passed');
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
@@ -44,23 +46,23 @@ export default function SettingsChats(props) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined))
|
||||
return showError('部分保存失败,请重试');
|
||||
return showError(t('部分保存失败,请重试'));
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
showSuccess(t('保存成功'));
|
||||
props.refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
showError(t('保存失败,请重试'));
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error('Validation failed:', error);
|
||||
showError('请检查输入');
|
||||
showError(t('请检查输入'));
|
||||
});
|
||||
} catch (error) {
|
||||
showError('请检查输入');
|
||||
showError(t('请检查输入'));
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
@@ -104,19 +106,19 @@ export default function SettingsChats(props) {
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'令牌聊天设置'}>
|
||||
<Form.Section text={t('令牌聊天设置')}>
|
||||
<Banner
|
||||
type='warning'
|
||||
description={'必须将上方聊天链接全部设置为空,才能使用下方聊天设置功能'}
|
||||
description={t('必须将上方聊天链接全部设置为空,才能使用下方聊天设置功能')}
|
||||
/>
|
||||
<Banner
|
||||
type='info'
|
||||
description={'链接中的{key}将自动替换为sk-xxxx,{address}将自动替换为系统设置的服务器地址,末尾不带/和/v1'}
|
||||
description={t('链接中的{key}将自动替换为sk-xxxx,{address}将自动替换为系统设置的服务器地址,末尾不带/和/v1')}
|
||||
/>
|
||||
<Form.TextArea
|
||||
label={'聊天配置'}
|
||||
label={t('聊天配置')}
|
||||
extraText={''}
|
||||
placeholder={'为一个 JSON 文本'}
|
||||
placeholder={t('为一个 JSON 文本')}
|
||||
field={'Chats'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
@@ -126,7 +128,7 @@ export default function SettingsChats(props) {
|
||||
validator: (rule, value) => {
|
||||
return verifyJSON(value);
|
||||
},
|
||||
message: '不是合法的 JSON 字符串'
|
||||
message: t('不是合法的 JSON 字符串')
|
||||
}
|
||||
]}
|
||||
onChange={(value) =>
|
||||
@@ -140,7 +142,7 @@ export default function SettingsChats(props) {
|
||||
</Form>
|
||||
<Space>
|
||||
<Button onClick={onSubmit}>
|
||||
保存聊天设置
|
||||
{t('保存聊天设置')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Spin>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
compareObjects,
|
||||
API,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from '../../../helpers';
|
||||
|
||||
export default function SettingsCreditLimit(props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [inputs, setInputs] = useState({
|
||||
QuotaForNewUser: '',
|
||||
@@ -21,7 +23,7 @@ export default function SettingsCreditLimit(props) {
|
||||
|
||||
function onSubmit() {
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
@@ -40,13 +42,13 @@ export default function SettingsCreditLimit(props) {
|
||||
if (requestQueue.length === 1) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined)) return showError('部分保存失败,请重试');
|
||||
if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
showSuccess(t('保存成功'));
|
||||
props.refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
showError(t('保存失败,请重试'));
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -72,11 +74,11 @@ export default function SettingsCreditLimit(props) {
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'额度设置'}>
|
||||
<Form.Section text={t('额度设置')}>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Form.InputNumber
|
||||
label={'新用户初始额度'}
|
||||
label={t('新用户初始额度')}
|
||||
field={'QuotaForNewUser'}
|
||||
step={1}
|
||||
min={0}
|
||||
@@ -92,12 +94,12 @@ export default function SettingsCreditLimit(props) {
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.InputNumber
|
||||
label={'请求预扣费额度'}
|
||||
label={t('请求预扣费额度')}
|
||||
field={'PreConsumedQuota'}
|
||||
step={1}
|
||||
min={0}
|
||||
suffix={'Token'}
|
||||
extraText={'请求结束后多退少补'}
|
||||
extraText={t('请求结束后多退少补')}
|
||||
placeholder={''}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
@@ -109,13 +111,13 @@ export default function SettingsCreditLimit(props) {
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.InputNumber
|
||||
label={'邀请新用户奖励额度'}
|
||||
label={t('邀请新用户奖励额度')}
|
||||
field={'QuotaForInviter'}
|
||||
step={1}
|
||||
min={0}
|
||||
suffix={'Token'}
|
||||
extraText={''}
|
||||
placeholder={'例如:2000'}
|
||||
placeholder={t('例如:2000')}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
@@ -126,13 +128,13 @@ export default function SettingsCreditLimit(props) {
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.InputNumber
|
||||
label={'新用户使用邀请码奖励额度'}
|
||||
label={t('新用户使用邀请码奖励额度')}
|
||||
field={'QuotaForInvitee'}
|
||||
step={1}
|
||||
min={0}
|
||||
suffix={'Token'}
|
||||
extraText={''}
|
||||
placeholder={'例如:1000'}
|
||||
placeholder={t('例如:1000')}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
@@ -145,7 +147,7 @@ export default function SettingsCreditLimit(props) {
|
||||
|
||||
<Row>
|
||||
<Button size='default' onClick={onSubmit}>
|
||||
保存额度设置
|
||||
{t('保存额度设置')}
|
||||
</Button>
|
||||
</Row>
|
||||
</Form.Section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Button, Col, Form, Row, Spin, Tag } from '@douyinfe/semi-ui';
|
||||
import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
compareObjects,
|
||||
API,
|
||||
@@ -7,12 +7,15 @@ import {
|
||||
showSuccess,
|
||||
showWarning,
|
||||
} from '../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function DataDashboard(props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const optionsDataExportDefaultTime = [
|
||||
{ key: 'hour', label: '小时', value: 'hour' },
|
||||
{ key: 'day', label: '天', value: 'day' },
|
||||
{ key: 'week', label: '周', value: 'week' },
|
||||
{ key: 'hour', label: t('小时'), value: 'hour' },
|
||||
{ key: 'day', label: t('天'), value: 'day' },
|
||||
{ key: 'week', label: t('周'), value: 'week' },
|
||||
];
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [inputs, setInputs] = useState({
|
||||
@@ -25,7 +28,7 @@ export default function DataDashboard(props) {
|
||||
|
||||
function onSubmit() {
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
@@ -44,13 +47,13 @@ export default function DataDashboard(props) {
|
||||
if (requestQueue.length === 1) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined)) return showError('部分保存失败,请重试');
|
||||
if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
showSuccess(t('保存成功'));
|
||||
props.refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
showError(t('保存失败,请重试'));
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -81,12 +84,12 @@ export default function DataDashboard(props) {
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'数据看板设置'}>
|
||||
<Form.Section text={t('数据看板设置')}>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'DataExportEnabled'}
|
||||
label={'启用数据看板(实验性)'}
|
||||
label={t('启用数据看板(实验性)')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -102,12 +105,12 @@ export default function DataDashboard(props) {
|
||||
<Row>
|
||||
<Col span={8}>
|
||||
<Form.InputNumber
|
||||
label={'数据看板更新间隔 '}
|
||||
label={t('数据看板更新间隔')}
|
||||
step={1}
|
||||
min={1}
|
||||
suffix={'分钟'}
|
||||
extraText={'设置过短会影响数据库性能'}
|
||||
placeholder={'数据看板更新间隔'}
|
||||
suffix={t('分钟')}
|
||||
extraText={t('设置过短会影响数据库性能')}
|
||||
placeholder={t('数据看板更新间隔')}
|
||||
field={'DataExportInterval'}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
@@ -119,11 +122,11 @@ export default function DataDashboard(props) {
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Select
|
||||
label='数据看板默认时间粒度'
|
||||
label={t('数据看板默认时间粒度')}
|
||||
optionList={optionsDataExportDefaultTime}
|
||||
field={'DataExportDefaultTime'}
|
||||
extraText={'仅修改展示粒度,统计精确到小时'}
|
||||
placeholder={'数据看板默认时间粒度'}
|
||||
extraText={t('仅修改展示粒度,统计精确到小时')}
|
||||
placeholder={t('数据看板默认时间粒度')}
|
||||
style={{ width: 180 }}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
@@ -136,7 +139,7 @@ export default function DataDashboard(props) {
|
||||
</Row>
|
||||
<Row>
|
||||
<Button size='default' onClick={onSubmit}>
|
||||
保存数据看板设置
|
||||
{t('保存数据看板设置')}
|
||||
</Button>
|
||||
</Row>
|
||||
</Form.Section>
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
showSuccess,
|
||||
showWarning,
|
||||
} from '../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SettingsDrawing(props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [inputs, setInputs] = useState({
|
||||
DrawingEnabled: false,
|
||||
@@ -23,7 +25,7 @@ export default function SettingsDrawing(props) {
|
||||
|
||||
function onSubmit() {
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
@@ -42,13 +44,13 @@ export default function SettingsDrawing(props) {
|
||||
if (requestQueue.length === 1) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined)) return showError('部分保存失败,请重试');
|
||||
if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
showSuccess(t('保存成功'));
|
||||
props.refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
showError(t('保存失败,请重试'));
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -67,6 +69,7 @@ export default function SettingsDrawing(props) {
|
||||
refForm.current.setValues(currentInputs);
|
||||
localStorage.setItem('mj_notify_enabled', String(inputs.MjNotifyEnabled));
|
||||
}, [props.options]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spin spinning={loading}>
|
||||
@@ -75,12 +78,12 @@ export default function SettingsDrawing(props) {
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'绘图设置'}>
|
||||
<Form.Section text={t('绘图设置')}>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'DrawingEnabled'}
|
||||
label={'启用绘图功能'}
|
||||
label={t('启用绘图功能')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -95,7 +98,7 @@ export default function SettingsDrawing(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'MjNotifyEnabled'}
|
||||
label={'允许回调(会泄露服务器 IP 地址)'}
|
||||
label={t('允许回调(会泄露服务器 IP 地址)')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -110,7 +113,7 @@ export default function SettingsDrawing(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'MjAccountFilterEnabled'}
|
||||
label={'允许 AccountFilter 参数'}
|
||||
label={t('允许 AccountFilter 参数')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -125,7 +128,7 @@ export default function SettingsDrawing(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'MjForwardUrlEnabled'}
|
||||
label={'开启之后将上游地址替换为服务器地址'}
|
||||
label={t('开启之后将上游地址替换为服务器地址')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -142,8 +145,8 @@ export default function SettingsDrawing(props) {
|
||||
field={'MjModeClearEnabled'}
|
||||
label={
|
||||
<>
|
||||
开启之后会清除用户提示词中的 <Tag>--fast</Tag> 、
|
||||
<Tag>--relax</Tag> 以及 <Tag>--turbo</Tag> 参数
|
||||
{t('开启之后会清除用户提示词中的')} <Tag>--fast</Tag> 、
|
||||
<Tag>--relax</Tag> {t('以及')} <Tag>--turbo</Tag> {t('参数')}
|
||||
</>
|
||||
}
|
||||
size='default'
|
||||
@@ -160,11 +163,7 @@ export default function SettingsDrawing(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'MjActionCheckSuccessEnabled'}
|
||||
label={
|
||||
<>
|
||||
检测必须等待绘图成功才能进行放大等操作
|
||||
</>
|
||||
}
|
||||
label={t('检测必须等待绘图成功才能进行放大等操作')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -179,7 +178,7 @@ export default function SettingsDrawing(props) {
|
||||
</Row>
|
||||
<Row>
|
||||
<Button size='default' onClick={onSubmit}>
|
||||
保存绘图设置
|
||||
{t('保存绘图设置')}
|
||||
</Button>
|
||||
</Row>
|
||||
</Form.Section>
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
showSuccess,
|
||||
showWarning,
|
||||
} from '../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function GeneralSettings(props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [inputs, setInputs] = useState({
|
||||
TopUpLink: '',
|
||||
@@ -22,13 +24,15 @@ export default function GeneralSettings(props) {
|
||||
});
|
||||
const refForm = useRef();
|
||||
const [inputsRow, setInputsRow] = useState(inputs);
|
||||
|
||||
function onChange(value, e) {
|
||||
const name = e.target.id;
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
@@ -47,13 +51,13 @@ export default function GeneralSettings(props) {
|
||||
if (requestQueue.length === 1) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined)) return showError('部分保存失败,请重试');
|
||||
if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
showSuccess(t('保存成功'));
|
||||
props.refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
showError(t('保存失败,请重试'));
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -71,26 +75,27 @@ export default function GeneralSettings(props) {
|
||||
setInputsRow(structuredClone(currentInputs));
|
||||
refForm.current.setValues(currentInputs);
|
||||
}, [props.options]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spin spinning={loading}>
|
||||
<Banner
|
||||
type='warning'
|
||||
description={'聊天链接功能已经弃用,请使用下方聊天设置功能'}
|
||||
description={t('聊天链接功能已经弃用,请使用下方聊天设置功能')}
|
||||
/>
|
||||
<Form
|
||||
values={inputs}
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'通用设置'}>
|
||||
<Form.Section text={t('通用设置')}>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Input
|
||||
field={'TopUpLink'}
|
||||
label={'充值链接'}
|
||||
label={t('充值链接')}
|
||||
initValue={''}
|
||||
placeholder={'例如发卡网站的购买链接'}
|
||||
placeholder={t('例如发卡网站的购买链接')}
|
||||
onChange={onChange}
|
||||
showClear
|
||||
/>
|
||||
@@ -98,9 +103,9 @@ export default function GeneralSettings(props) {
|
||||
<Col span={8}>
|
||||
<Form.Input
|
||||
field={'ChatLink'}
|
||||
label={'默认聊天页面链接'}
|
||||
label={t('默认聊天页面链接')}
|
||||
initValue={''}
|
||||
placeholder='例如 ChatGPT Next Web 的部署地址'
|
||||
placeholder={t('例如 ChatGPT Next Web 的部署地址')}
|
||||
onChange={onChange}
|
||||
showClear
|
||||
/>
|
||||
@@ -108,9 +113,9 @@ export default function GeneralSettings(props) {
|
||||
<Col span={8}>
|
||||
<Form.Input
|
||||
field={'ChatLink2'}
|
||||
label={'聊天页面 2 链接'}
|
||||
label={t('聊天页面 2 链接')}
|
||||
initValue={''}
|
||||
placeholder='例如 ChatGPT Next Web 的部署地址'
|
||||
placeholder={t('例如 ChatGPT Next Web 的部署地址')}
|
||||
onChange={onChange}
|
||||
showClear
|
||||
/>
|
||||
@@ -118,9 +123,9 @@ export default function GeneralSettings(props) {
|
||||
<Col span={8}>
|
||||
<Form.Input
|
||||
field={'QuotaPerUnit'}
|
||||
label={'单位美元额度'}
|
||||
label={t('单位美元额度')}
|
||||
initValue={''}
|
||||
placeholder='一单位货币能兑换的额度'
|
||||
placeholder={t('一单位货币能兑换的额度')}
|
||||
onChange={onChange}
|
||||
showClear
|
||||
/>
|
||||
@@ -128,9 +133,9 @@ export default function GeneralSettings(props) {
|
||||
<Col span={8}>
|
||||
<Form.Input
|
||||
field={'RetryTimes'}
|
||||
label={'失败重试次数'}
|
||||
label={t('失败重试次数')}
|
||||
initValue={''}
|
||||
placeholder='失败重试次数'
|
||||
placeholder={t('失败重试次数')}
|
||||
onChange={onChange}
|
||||
showClear
|
||||
/>
|
||||
@@ -140,7 +145,7 @@ export default function GeneralSettings(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'DisplayInCurrencyEnabled'}
|
||||
label={'以货币形式显示额度'}
|
||||
label={t('以货币形式显示额度')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -155,7 +160,7 @@ export default function GeneralSettings(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'DisplayTokenStatEnabled'}
|
||||
label={'Billing 相关 API 显示令牌额度而非用户额度'}
|
||||
label={t('额度查询接口返回令牌额度而非用户额度')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -170,7 +175,7 @@ export default function GeneralSettings(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'DefaultCollapseSidebar'}
|
||||
label={'默认折叠侧边栏'}
|
||||
label={t('默认折叠侧边栏')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -185,7 +190,7 @@ export default function GeneralSettings(props) {
|
||||
</Row>
|
||||
<Row>
|
||||
<Button size='default' onClick={onSubmit}>
|
||||
保存通用设置
|
||||
{t('保存通用设置')}
|
||||
</Button>
|
||||
</Row>
|
||||
</Form.Section>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Button, Col, Form, Row, Spin, DatePicker } from '@douyinfe/semi-ui';
|
||||
import dayjs from 'dayjs';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
compareObjects,
|
||||
API,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
} from '../../../helpers';
|
||||
|
||||
export default function SettingsLog(props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingCleanHistoryLog, setLoadingCleanHistoryLog] = useState(false);
|
||||
const [inputs, setInputs] = useState({
|
||||
@@ -24,7 +26,7 @@ export default function SettingsLog(props) {
|
||||
(item) => item.key !== 'historyTimestamp',
|
||||
);
|
||||
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
@@ -43,13 +45,13 @@ export default function SettingsLog(props) {
|
||||
if (requestQueue.length === 1) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined)) return showError('部分保存失败,请重试');
|
||||
if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
showSuccess(t('保存成功'));
|
||||
props.refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
showError(t('保存失败,请重试'));
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -58,16 +60,16 @@ export default function SettingsLog(props) {
|
||||
async function onCleanHistoryLog() {
|
||||
try {
|
||||
setLoadingCleanHistoryLog(true);
|
||||
if (!inputs.historyTimestamp) throw new Error('请选择日志记录时间');
|
||||
if (!inputs.historyTimestamp) throw new Error(t('请选择日志记录时间'));
|
||||
const res = await API.delete(
|
||||
`/api/log/?target_timestamp=${Date.parse(inputs.historyTimestamp) / 1000}`,
|
||||
);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
showSuccess(`${data} 条日志已清理!`);
|
||||
showSuccess(`${data} ${t('条日志已清理!')}`);
|
||||
return;
|
||||
} else {
|
||||
throw new Error('日志清理失败:' + message);
|
||||
throw new Error(t('日志清理失败:') + message);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
@@ -96,12 +98,12 @@ export default function SettingsLog(props) {
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'日志设置'}>
|
||||
<Form.Section text={t('日志设置')}>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'LogConsumeEnabled'}
|
||||
label={'启用额度消费日志记录'}
|
||||
label={t('启用额度消费日志记录')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -116,7 +118,7 @@ export default function SettingsLog(props) {
|
||||
<Col span={8}>
|
||||
<Spin spinning={loadingCleanHistoryLog}>
|
||||
<Form.DatePicker
|
||||
label='日志记录时间'
|
||||
label={t('日志记录时间')}
|
||||
field={'historyTimestamp'}
|
||||
type='dateTime'
|
||||
inputReadOnly={true}
|
||||
@@ -128,7 +130,7 @@ export default function SettingsLog(props) {
|
||||
}}
|
||||
/>
|
||||
<Button size='default' onClick={onCleanHistoryLog}>
|
||||
清除历史日志
|
||||
{t('清除历史日志')}
|
||||
</Button>
|
||||
</Spin>
|
||||
</Col>
|
||||
@@ -136,7 +138,7 @@ export default function SettingsLog(props) {
|
||||
|
||||
<Row>
|
||||
<Button size='default' onClick={onSubmit}>
|
||||
保存日志设置
|
||||
{t('保存日志设置')}
|
||||
</Button>
|
||||
</Row>
|
||||
</Form.Section>
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
showSuccess,
|
||||
showWarning,
|
||||
} from '../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SettingsMonitoring(props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [inputs, setInputs] = useState({
|
||||
ChannelDisableThreshold: '',
|
||||
@@ -21,7 +23,7 @@ export default function SettingsMonitoring(props) {
|
||||
|
||||
function onSubmit() {
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
@@ -40,13 +42,13 @@ export default function SettingsMonitoring(props) {
|
||||
if (requestQueue.length === 1) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined)) return showError('部分保存失败,请重试');
|
||||
if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
showSuccess(t('保存成功'));
|
||||
props.refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
showError(t('保存失败,请重试'));
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -64,6 +66,7 @@ export default function SettingsMonitoring(props) {
|
||||
setInputsRow(structuredClone(currentInputs));
|
||||
refForm.current.setValues(currentInputs);
|
||||
}, [props.options]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spin spinning={loading}>
|
||||
@@ -72,15 +75,15 @@ export default function SettingsMonitoring(props) {
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'监控设置'}>
|
||||
<Form.Section text={t('监控设置')}>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.InputNumber
|
||||
label={'最长响应时间'}
|
||||
label={t('最长响应时间')}
|
||||
step={1}
|
||||
min={0}
|
||||
suffix={'秒'}
|
||||
extraText={'当运行通道全部测试时,超过此时间将自动禁用通道'}
|
||||
suffix={t('秒')}
|
||||
extraText={t('当运行通道全部测试时,超过此时间将自动禁用通道')}
|
||||
placeholder={''}
|
||||
field={'ChannelDisableThreshold'}
|
||||
onChange={(value) =>
|
||||
@@ -93,11 +96,11 @@ export default function SettingsMonitoring(props) {
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.InputNumber
|
||||
label={'额度提醒阈值'}
|
||||
label={t('额度提醒阈值')}
|
||||
step={1}
|
||||
min={0}
|
||||
suffix={'Token'}
|
||||
extraText={'低于此额度时将发送邮件提醒用户'}
|
||||
extraText={t('低于此额度时将发送邮件提醒用户')}
|
||||
placeholder={''}
|
||||
field={'QuotaRemindThreshold'}
|
||||
onChange={(value) =>
|
||||
@@ -113,7 +116,7 @@ export default function SettingsMonitoring(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'AutomaticDisableChannelEnabled'}
|
||||
label={'失败时自动禁用通道'}
|
||||
label={t('失败时自动禁用通道')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -128,7 +131,7 @@ export default function SettingsMonitoring(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'AutomaticEnableChannelEnabled'}
|
||||
label={'成功时自动启用通道'}
|
||||
label={t('成功时自动启用通道')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -143,7 +146,7 @@ export default function SettingsMonitoring(props) {
|
||||
</Row>
|
||||
<Row>
|
||||
<Button size='default' onClick={onSubmit}>
|
||||
保存监控设置
|
||||
{t('保存监控设置')}
|
||||
</Button>
|
||||
</Row>
|
||||
</Form.Section>
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
showSuccess,
|
||||
showWarning,
|
||||
} from '../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SettingsSensitiveWords(props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [inputs, setInputs] = useState({
|
||||
CheckSensitiveEnabled: false,
|
||||
@@ -20,7 +22,7 @@ export default function SettingsSensitiveWords(props) {
|
||||
|
||||
function onSubmit() {
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
@@ -39,13 +41,13 @@ export default function SettingsSensitiveWords(props) {
|
||||
if (requestQueue.length === 1) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined)) return showError('部分保存失败,请重试');
|
||||
if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
showSuccess(t('保存成功'));
|
||||
props.refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
showError(t('保存失败,请重试'));
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -71,12 +73,12 @@ export default function SettingsSensitiveWords(props) {
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'屏蔽词过滤设置'}>
|
||||
<Form.Section text={t('屏蔽词过滤设置')}>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'CheckSensitiveEnabled'}
|
||||
label={'启用屏蔽词过滤功能'}
|
||||
label={t('启用屏蔽词过滤功能')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -91,7 +93,7 @@ export default function SettingsSensitiveWords(props) {
|
||||
<Col span={8}>
|
||||
<Form.Switch
|
||||
field={'CheckSensitiveOnPromptEnabled'}
|
||||
label={'启用 Prompt 检查'}
|
||||
label={t('启用 Prompt 检查')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
@@ -107,9 +109,9 @@ export default function SettingsSensitiveWords(props) {
|
||||
<Row>
|
||||
<Col span={16}>
|
||||
<Form.TextArea
|
||||
label={'屏蔽词列表'}
|
||||
extraText={'一行一个屏蔽词,不需要符号分割'}
|
||||
placeholder={'一行一个屏蔽词,不需要符号分割'}
|
||||
label={t('屏蔽词列表')}
|
||||
extraText={t('一行一个屏蔽词,不需要符号分割')}
|
||||
placeholder={t('一行一个屏蔽词,不需要符号分割')}
|
||||
field={'SensitiveWords'}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
@@ -124,7 +126,7 @@ export default function SettingsSensitiveWords(props) {
|
||||
</Row>
|
||||
<Row>
|
||||
<Button size='default' onClick={onSubmit}>
|
||||
保存屏蔽词过滤设置
|
||||
{t('保存屏蔽词过滤设置')}
|
||||
</Button>
|
||||
</Row>
|
||||
</Form.Section>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Layout, TabPane, Tabs } from '@douyinfe/semi-ui';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SystemSetting from '../../components/SystemSetting';
|
||||
import { isRoot } from '../../helpers';
|
||||
import OtherSetting from '../../components/OtherSetting';
|
||||
import PersonalSetting from '../../components/PersonalSetting';
|
||||
import OperationSetting from '../../components/OperationSetting';
|
||||
|
||||
const Setting = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [tabActiveKey, setTabActiveKey] = useState('1');
|
||||
let panes = [
|
||||
{
|
||||
tab: '个人设置',
|
||||
tab: t('个人设置'),
|
||||
content: <PersonalSetting />,
|
||||
itemKey: 'personal',
|
||||
},
|
||||
@@ -21,17 +24,17 @@ const Setting = () => {
|
||||
|
||||
if (isRoot()) {
|
||||
panes.push({
|
||||
tab: '运营设置',
|
||||
tab: t('运营设置'),
|
||||
content: <OperationSetting />,
|
||||
itemKey: 'operation',
|
||||
});
|
||||
panes.push({
|
||||
tab: '系统设置',
|
||||
tab: t('系统设置'),
|
||||
content: <SystemSetting />,
|
||||
itemKey: 'system',
|
||||
});
|
||||
panes.push({
|
||||
tab: '其他设置',
|
||||
tab: t('其他设置'),
|
||||
content: <OtherSetting />,
|
||||
itemKey: 'other',
|
||||
});
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import React from 'react';
|
||||
import TokensTable from '../../components/TokensTable';
|
||||
import { Banner, Layout } from '@douyinfe/semi-ui';
|
||||
const Token = () => (
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
import { useTranslation } from 'react-i18next';
|
||||
const Token = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<Banner
|
||||
type='warning'
|
||||
description='令牌无法精确控制使用额度,请勿直接将令牌分发给用户。'
|
||||
description={t('令牌无法精确控制使用额度,只允许自用,请勿直接将令牌分发给他人。')}
|
||||
/>
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<TokensTable />
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Token;
|
||||
|
||||
@@ -21,8 +21,10 @@ import {
|
||||
import Title from '@douyinfe/semi-ui/lib/es/typography/title';
|
||||
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const TopUp = () => {
|
||||
const { t } = useTranslation();
|
||||
const [redemptionCode, setRedemptionCode] = useState('');
|
||||
const [topUpCode, setTopUpCode] = useState('');
|
||||
const [topUpCount, setTopUpCount] = useState(0);
|
||||
@@ -38,7 +40,7 @@ const TopUp = () => {
|
||||
|
||||
const topUp = async () => {
|
||||
if (redemptionCode === '') {
|
||||
showInfo('请输入兑换码!');
|
||||
showInfo(t('请输入兑换码!'));
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
@@ -48,10 +50,10 @@ const TopUp = () => {
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
showSuccess('兑换成功!');
|
||||
showSuccess(t('兑换成功!'));
|
||||
Modal.success({
|
||||
title: '兑换成功!',
|
||||
content: '成功兑换额度:' + renderQuota(data),
|
||||
title: t('兑换成功!'),
|
||||
content: t('成功兑换额度:') + renderQuota(data),
|
||||
centered: true,
|
||||
});
|
||||
setUserQuota((quota) => {
|
||||
@@ -62,7 +64,7 @@ const TopUp = () => {
|
||||
showError(message);
|
||||
}
|
||||
} catch (err) {
|
||||
showError('请求失败');
|
||||
showError(t('请求失败'));
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -70,7 +72,7 @@ const TopUp = () => {
|
||||
|
||||
const openTopUpLink = () => {
|
||||
if (!topUpLink) {
|
||||
showError('超级管理员未设置充值链接!');
|
||||
showError(t('超级管理员未设置充值链接!'));
|
||||
return;
|
||||
}
|
||||
window.open(topUpLink, '_blank');
|
||||
@@ -78,12 +80,12 @@ const TopUp = () => {
|
||||
|
||||
const preTopUp = async (payment) => {
|
||||
if (!enableOnlineTopUp) {
|
||||
showError('管理员未开启在线充值!');
|
||||
showError(t('管理员未开启在线充值!'));
|
||||
return;
|
||||
}
|
||||
await getAmount();
|
||||
if (topUpCount < minTopUp) {
|
||||
showError('充值数量不能小于' + minTopUp);
|
||||
showError(t('充值数量不能小于') + minTopUp);
|
||||
return;
|
||||
}
|
||||
setPayWay(payment);
|
||||
@@ -174,7 +176,7 @@ const TopUp = () => {
|
||||
|
||||
const renderAmount = () => {
|
||||
// console.log(amount);
|
||||
return amount + '元';
|
||||
return amount + ' ' + t('元');
|
||||
};
|
||||
|
||||
const getAmount = async (value) => {
|
||||
@@ -214,11 +216,11 @@ const TopUp = () => {
|
||||
<div>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<h3>我的钱包</h3>
|
||||
<h3>{t('我的钱包')}</h3>
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<Modal
|
||||
title='确定要充值吗'
|
||||
title={t('确定要充值吗')}
|
||||
visible={open}
|
||||
onOk={onlineTopUp}
|
||||
onCancel={handleCancel}
|
||||
@@ -226,24 +228,24 @@ const TopUp = () => {
|
||||
size={'small'}
|
||||
centered={true}
|
||||
>
|
||||
<p>充值数量:{topUpCount}</p>
|
||||
<p>实付金额:{renderAmount()}</p>
|
||||
<p>是否确认充值?</p>
|
||||
<p>{t('充值数量')}:{topUpCount}</p>
|
||||
<p>{t('实付金额')}:{renderAmount()}</p>
|
||||
<p>{t('是否确认充值?')}</p>
|
||||
</Modal>
|
||||
<div
|
||||
style={{ marginTop: 20, display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<Card style={{ width: '500px', padding: '20px' }}>
|
||||
<Title level={3} style={{ textAlign: 'center' }}>
|
||||
余额 {renderQuota(userQuota)}
|
||||
{t('余额')} {renderQuota(userQuota)}
|
||||
</Title>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Divider>兑换余额</Divider>
|
||||
<Divider>{t('兑换余额')}</Divider>
|
||||
<Form>
|
||||
<Form.Input
|
||||
field={'redemptionCode'}
|
||||
label={'兑换码'}
|
||||
placeholder='兑换码'
|
||||
label={t('兑换码')}
|
||||
placeholder={t('兑换码')}
|
||||
name='redemptionCode'
|
||||
value={redemptionCode}
|
||||
onChange={(value) => {
|
||||
@@ -257,7 +259,7 @@ const TopUp = () => {
|
||||
theme={'solid'}
|
||||
onClick={openTopUpLink}
|
||||
>
|
||||
获取兑换码
|
||||
{t('获取兑换码')}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
@@ -266,21 +268,19 @@ const TopUp = () => {
|
||||
onClick={topUp}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? '兑换中...' : '兑换'}
|
||||
{isSubmitting ? t('兑换中...') : t('兑换')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Divider>在线充值</Divider>
|
||||
<Divider>{t('在线充值')}</Divider>
|
||||
<Form>
|
||||
<Form.Input
|
||||
disabled={!enableOnlineTopUp}
|
||||
field={'redemptionCount'}
|
||||
label={'实付金额:' + renderAmount()}
|
||||
placeholder={
|
||||
'充值数量,最低 ' + renderQuotaWithAmount(minTopUp)
|
||||
}
|
||||
label={t('实付金额:') + ' ' + renderAmount()}
|
||||
placeholder={t('充值数量,最低 ') + renderQuotaWithAmount(minTopUp)}
|
||||
name='redemptionCount'
|
||||
type={'number'}
|
||||
value={topUpCount}
|
||||
@@ -300,7 +300,7 @@ const TopUp = () => {
|
||||
preTopUp('zfb');
|
||||
}}
|
||||
>
|
||||
支付宝
|
||||
{t('支付宝')}
|
||||
</Button>
|
||||
<Button
|
||||
style={{
|
||||
@@ -312,7 +312,7 @@ const TopUp = () => {
|
||||
preTopUp('wx');
|
||||
}}
|
||||
>
|
||||
微信
|
||||
{t('微信')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Spin,
|
||||
Typography,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const EditUser = (props) => {
|
||||
const userId = props.editingUser.id;
|
||||
@@ -120,11 +121,13 @@ const EditUser = (props) => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SideSheet
|
||||
placement={'right'}
|
||||
title={<Title level={3}>{'编辑用户'}</Title>}
|
||||
title={<Title level={3}>{t('编辑用户')}</Title>}
|
||||
headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
|
||||
bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
|
||||
visible={props.visible}
|
||||
@@ -132,7 +135,7 @@ const EditUser = (props) => {
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Space>
|
||||
<Button theme='solid' size={'large'} onClick={submit}>
|
||||
提交
|
||||
{t('提交')}
|
||||
</Button>
|
||||
<Button
|
||||
theme='solid'
|
||||
@@ -140,7 +143,7 @@ const EditUser = (props) => {
|
||||
type={'tertiary'}
|
||||
onClick={handleCancel}
|
||||
>
|
||||
取消
|
||||
{t('取消')}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
@@ -151,35 +154,35 @@ const EditUser = (props) => {
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>用户名</Typography.Text>
|
||||
<Typography.Text>{t('用户名')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label='用户名'
|
||||
label={t('用户名')}
|
||||
name='username'
|
||||
placeholder={'请输入新的用户名'}
|
||||
placeholder={t('请输入新的用户名')}
|
||||
onChange={(value) => handleInputChange('username', value)}
|
||||
value={username}
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>密码</Typography.Text>
|
||||
<Typography.Text>{t('密码')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label='密码'
|
||||
label={t('密码')}
|
||||
name='password'
|
||||
type={'password'}
|
||||
placeholder={'请输入新的密码,最短 8 位'}
|
||||
placeholder={t('请输入新的密码,最短 8 位')}
|
||||
onChange={(value) => handleInputChange('password', value)}
|
||||
value={password}
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>显示名称</Typography.Text>
|
||||
<Typography.Text>{t('显示名称')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label='显示名称'
|
||||
label={t('显示名称')}
|
||||
name='display_name'
|
||||
placeholder={'请输入新的显示名称'}
|
||||
placeholder={t('请输入新的显示名称')}
|
||||
onChange={(value) => handleInputChange('display_name', value)}
|
||||
value={display_name}
|
||||
autoComplete='new-password'
|
||||
@@ -187,76 +190,76 @@ const EditUser = (props) => {
|
||||
{userId && (
|
||||
<>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>分组</Typography.Text>
|
||||
<Typography.Text>{t('分组')}</Typography.Text>
|
||||
</div>
|
||||
<Select
|
||||
placeholder={'请选择分组'}
|
||||
placeholder={t('请选择分组')}
|
||||
name='group'
|
||||
fluid
|
||||
search
|
||||
selection
|
||||
allowAdditions
|
||||
additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
|
||||
additionLabel={t('请在系统设置页面编辑分组倍率以添加新的分组:')}
|
||||
onChange={(value) => handleInputChange('group', value)}
|
||||
value={inputs.group}
|
||||
autoComplete='new-password'
|
||||
optionList={groupOptions}
|
||||
/>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>{`剩余额度${renderQuotaWithPrompt(quota)}`}</Typography.Text>
|
||||
<Typography.Text>{`${t('剩余额度')}${renderQuotaWithPrompt(quota)}`}</Typography.Text>
|
||||
</div>
|
||||
<Space>
|
||||
<Input
|
||||
name='quota'
|
||||
placeholder={'请输入新的剩余额度'}
|
||||
placeholder={t('请输入新的剩余额度')}
|
||||
onChange={(value) => handleInputChange('quota', value)}
|
||||
value={quota}
|
||||
type={'number'}
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<Button onClick={openAddQuotaModal}>添加额度</Button>
|
||||
<Button onClick={openAddQuotaModal}>{t('添加额度')}</Button>
|
||||
</Space>
|
||||
</>
|
||||
)}
|
||||
<Divider style={{ marginTop: 20 }}>以下信息不可修改</Divider>
|
||||
<Divider style={{ marginTop: 20 }}>{t('以下信息不可修改')}</Divider>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>已绑定的 GitHub 账户</Typography.Text>
|
||||
<Typography.Text>{t('已绑定的 GitHub 账户')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name='github_id'
|
||||
value={github_id}
|
||||
autoComplete='new-password'
|
||||
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
|
||||
placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
|
||||
readonly
|
||||
/>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>已绑定的微信账户</Typography.Text>
|
||||
<Typography.Text>{t('已绑定的微信账户')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name='wechat_id'
|
||||
value={wechat_id}
|
||||
autoComplete='new-password'
|
||||
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
|
||||
placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
|
||||
readonly
|
||||
/>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>已绑定的邮箱账户</Typography.Text>
|
||||
<Typography.Text>{t('已绑定的邮箱账户')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name='email'
|
||||
value={email}
|
||||
autoComplete='new-password'
|
||||
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
|
||||
placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
|
||||
readonly
|
||||
/>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>已绑定的Telegram账户</Typography.Text>
|
||||
<Typography.Text>{t('已绑定的Telegram账户')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name='telegram_id'
|
||||
value={telegram_id}
|
||||
autoComplete='new-password'
|
||||
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
|
||||
placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
|
||||
readonly
|
||||
/>
|
||||
</Spin>
|
||||
@@ -272,11 +275,11 @@ const EditUser = (props) => {
|
||||
closable={null}
|
||||
>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text>{`新额度${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + parseInt(addQuotaLocal))}`}</Typography.Text>
|
||||
<Typography.Text>{`${t('新额度')}${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + parseInt(addQuotaLocal))}`}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name='addQuotaLocal'
|
||||
placeholder={'需要添加的额度(支持负数)'}
|
||||
placeholder={t('需要添加的额度(支持负数)')}
|
||||
onChange={(value) => {
|
||||
setAddQuotaLocal(value);
|
||||
}}
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import React from 'react';
|
||||
import UsersTable from '../../components/UsersTable';
|
||||
import { Layout } from '@douyinfe/semi-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const User = () => (
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<h3>管理用户</h3>
|
||||
const User = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<h3>{t('管理用户')}</h3>
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<UsersTable />
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default User;
|
||||
|
||||
Reference in New Issue
Block a user