mirror of
https://github.com/linux-do/new-api.git
synced 2025-11-09 23:53:41 +08:00
merge upstream
Signed-off-by: wozulong <>
This commit is contained in:
@@ -208,7 +208,6 @@ const LoginForm = () => {
|
||||
</Text>
|
||||
</div>
|
||||
{status.github_oauth ||
|
||||
status.linuxdo_oauth ||
|
||||
status.wechat_login ||
|
||||
status.telegram_oauth ? (
|
||||
<>
|
||||
@@ -226,7 +225,6 @@ const LoginForm = () => {
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<IconGithubLogo />}
|
||||
style={{ margin: '0 5px' }}
|
||||
onClick={() =>
|
||||
onGitHubOAuthClicked(status.github_client_id)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ const OperationSetting = () => {
|
||||
SensitiveWords: '',
|
||||
MjNotifyEnabled: '',
|
||||
MjModeClearEnabled: '',
|
||||
MjForwardUrlEnabled: '',
|
||||
DrawingEnabled: '',
|
||||
DataExportEnabled: '',
|
||||
DataExportDefaultTime: 'hour',
|
||||
@@ -322,6 +323,12 @@ const OperationSetting = () => {
|
||||
name='MjNotifyEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.MjForwardUrlEnabled === 'true'}
|
||||
label='开启之后将上游地址替换为服务器地址'
|
||||
name='MjForwardUrlEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.MjModeClearEnabled === 'true'}
|
||||
label='开启之后会清除用户提示词中的--fast、--relax以及--turbo参数'
|
||||
|
||||
@@ -253,6 +253,8 @@ const UsersTable = () => {
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [searchGroup, setSearchGroup] = useState('');
|
||||
const [groupOptions, setGroupOptions] = useState([]);
|
||||
const [userCount, setUserCount] = useState(ITEMS_PER_PAGE);
|
||||
const [showAddUser, setShowAddUser] = useState(false);
|
||||
const [showEditUser, setShowEditUser] = useState(false);
|
||||
@@ -316,6 +318,7 @@ const UsersTable = () => {
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
});
|
||||
fetchGroups().then();
|
||||
}, []);
|
||||
|
||||
const manageUser = async (username, action, record) => {
|
||||
@@ -370,15 +373,17 @@ const UsersTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const searchUsers = async () => {
|
||||
if (searchKeyword === '') {
|
||||
const searchUsers = async (searchKeyword, searchGroup) => {
|
||||
if (searchKeyword === '' && searchGroup === '') {
|
||||
// if keyword is blank, load files instead.
|
||||
await loadUsers(0);
|
||||
setActivePage(1);
|
||||
return;
|
||||
}
|
||||
setSearching(true);
|
||||
const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
|
||||
const res = await API.get(
|
||||
`/api/user/search?keyword=${searchKeyword}&group=${searchGroup}`,
|
||||
);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setUsers(data);
|
||||
@@ -439,6 +444,25 @@ const UsersTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchGroups = async () => {
|
||||
try {
|
||||
let res = await API.get(`/api/group/`);
|
||||
// add 'all' option
|
||||
// res.data.data.unshift('all');
|
||||
if (res === undefined) {
|
||||
return;
|
||||
}
|
||||
setGroupOptions(
|
||||
res.data.data.map((group) => ({
|
||||
label: group,
|
||||
value: group,
|
||||
})),
|
||||
);
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddUser
|
||||
@@ -452,17 +476,44 @@ const UsersTable = () => {
|
||||
handleClose={closeEditUser}
|
||||
editingUser={editingUser}
|
||||
></EditUser>
|
||||
<Form onSubmit={searchUsers}>
|
||||
<Form.Input
|
||||
label='搜索关键字'
|
||||
icon='search'
|
||||
field='keyword'
|
||||
iconPosition='left'
|
||||
placeholder='搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...'
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={(value) => handleKeywordChange(value)}
|
||||
/>
|
||||
<Form
|
||||
onSubmit={() => {
|
||||
searchUsers(searchKeyword, searchGroup);
|
||||
}}
|
||||
labelPosition='left'
|
||||
>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Space>
|
||||
<Form.Input
|
||||
label='搜索关键字'
|
||||
icon='search'
|
||||
field='keyword'
|
||||
iconPosition='left'
|
||||
placeholder='搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...'
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={(value) => handleKeywordChange(value)}
|
||||
/>
|
||||
<Form.Select
|
||||
field='group'
|
||||
label='分组'
|
||||
optionList={groupOptions}
|
||||
onChange={(value) => {
|
||||
setSearchGroup(value);
|
||||
searchUsers(searchKeyword, value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
label='查询'
|
||||
type='primary'
|
||||
htmlType='submit'
|
||||
className='btn-margin-right'
|
||||
style={{ marginRight: 8 }}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<Table
|
||||
|
||||
@@ -22,6 +22,13 @@ export const CHANNEL_OPTIONS = [
|
||||
color: 'indigo',
|
||||
label: 'Anthropic Claude',
|
||||
},
|
||||
{
|
||||
key: 33,
|
||||
text: 'AWS Claude',
|
||||
value: 33,
|
||||
color: 'indigo',
|
||||
label: 'AWS Claude',
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
text: 'Azure OpenAI',
|
||||
@@ -43,6 +50,13 @@ export const CHANNEL_OPTIONS = [
|
||||
color: 'orange',
|
||||
label: 'Google Gemini',
|
||||
},
|
||||
{
|
||||
key: 34,
|
||||
text: 'Cohere',
|
||||
value: 34,
|
||||
color: 'purple',
|
||||
label: 'Cohere',
|
||||
},
|
||||
{
|
||||
key: 15,
|
||||
text: '百度文心千帆',
|
||||
|
||||
@@ -166,13 +166,12 @@ export const modelColorMap = {
|
||||
'dall-e': 'rgb(147,112,219)', // 深紫色
|
||||
'dall-e-2': 'rgb(147,112,219)', // 介于紫色和蓝色之间的色调
|
||||
'dall-e-3': 'rgb(153,50,204)', // 介于紫罗兰和洋红之间的色调
|
||||
midjourney: 'rgb(136,43,180)', // 介于紫罗兰和洋红之间的色调
|
||||
'gpt-3.5-turbo': 'rgb(184,227,167)', // 浅绿色
|
||||
'gpt-3.5-turbo-0301': 'rgb(131,220,131)', // 亮绿色
|
||||
'gpt-3.5-turbo-0613': 'rgb(60,179,113)', // 海洋绿
|
||||
'gpt-3.5-turbo-1106': 'rgb(32,178,170)', // 浅海洋绿
|
||||
'gpt-3.5-turbo-16k': 'rgb(252,200,149)', // 淡橙色
|
||||
'gpt-3.5-turbo-16k-0613': 'rgb(255,181,119)', // 淡桃色
|
||||
'gpt-3.5-turbo-16k': 'rgb(149,252,206)', // 淡橙色
|
||||
'gpt-3.5-turbo-16k-0613': 'rgb(119,255,214)', // 淡桃色
|
||||
'gpt-3.5-turbo-instruct': 'rgb(175,238,238)', // 粉蓝色
|
||||
'gpt-4': 'rgb(135,206,235)', // 天蓝色
|
||||
'gpt-4-0314': 'rgb(70,130,180)', // 钢蓝色
|
||||
@@ -203,6 +202,10 @@ export const modelColorMap = {
|
||||
'tts-1-hd': 'rgb(255,215,0)', // 金色
|
||||
'tts-1-hd-1106': 'rgb(255,223,0)', // 金黄色(略有区别)
|
||||
'whisper-1': 'rgb(245,245,220)', // 米色
|
||||
'claude-3-opus-20240229': 'rgb(255,132,31)', // 橙红色
|
||||
'claude-3-sonnet-20240229': 'rgb(253,135,93)', // 橙色
|
||||
'claude-3-haiku-20240307': 'rgb(255,175,146)', // 浅橙色
|
||||
'claude-2.1': 'rgb(255,209,190)', // 浅橙色(略有区别)
|
||||
};
|
||||
|
||||
export function stringToColor(str) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
Checkbox,
|
||||
Banner,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { Divider } from 'semantic-ui-react';
|
||||
|
||||
const MODEL_MAPPING_EXAMPLE = {
|
||||
'gpt-3.5-turbo-0301': 'gpt-3.5-turbo',
|
||||
@@ -29,6 +30,10 @@ const MODEL_MAPPING_EXAMPLE = {
|
||||
'gpt-4-32k-0314': 'gpt-4-32k',
|
||||
};
|
||||
|
||||
const STATUS_CODE_MAPPING_EXAMPLE = {
|
||||
400: '500',
|
||||
};
|
||||
|
||||
function type2secretPrompt(type) {
|
||||
// inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')
|
||||
switch (type) {
|
||||
@@ -40,6 +45,8 @@ function type2secretPrompt(type) {
|
||||
return '按照如下格式输入:APIKey-AppId,例如:fastgpt-0sp2gtvfdgyi4k30jwlgwf1i-64f335d84283f05518e9e041';
|
||||
case 23:
|
||||
return '按照如下格式输入:AppId|SecretId|SecretKey';
|
||||
case 33:
|
||||
return '按照如下格式输入:Ak|Sk|Region';
|
||||
default:
|
||||
return '请输入渠道对应的鉴权密钥';
|
||||
}
|
||||
@@ -58,9 +65,11 @@ const EditChannel = (props) => {
|
||||
type: 1,
|
||||
key: '',
|
||||
openai_organization: '',
|
||||
max_input_tokens: 0,
|
||||
base_url: '',
|
||||
other: '',
|
||||
model_mapping: '',
|
||||
status_code_mapping: '',
|
||||
models: [],
|
||||
auto_ban: 1,
|
||||
test_model: '',
|
||||
@@ -81,6 +90,7 @@ const EditChannel = (props) => {
|
||||
if (name === 'type' && inputs.models.length === 0) {
|
||||
let localModels = [];
|
||||
switch (value) {
|
||||
case 33:
|
||||
case 14:
|
||||
localModels = [
|
||||
'claude-instant-1.2',
|
||||
@@ -136,7 +146,24 @@ const EditChannel = (props) => {
|
||||
localModels = ['hunyuan'];
|
||||
break;
|
||||
case 24:
|
||||
localModels = ['gemini-pro', 'gemini-pro-vision'];
|
||||
localModels = [
|
||||
'gemini-1.0-pro-001',
|
||||
'gemini-1.0-pro-vision-001',
|
||||
'gemini-1.5-pro',
|
||||
'gemini-1.5-pro-latest',
|
||||
'gemini-pro',
|
||||
'gemini-pro-vision',
|
||||
];
|
||||
break;
|
||||
case 34:
|
||||
localModels = [
|
||||
'command-r',
|
||||
'command-r-plus',
|
||||
'command-light',
|
||||
'command-light-nightly',
|
||||
'command',
|
||||
'command-nightly',
|
||||
];
|
||||
break;
|
||||
case 25:
|
||||
localModels = [
|
||||
@@ -658,18 +685,22 @@ const EditChannel = (props) => {
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
)}
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>组织:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label='组织,可选,不填则为默认组织'
|
||||
name='openai_organization'
|
||||
placeholder='请输入组织org-xxx'
|
||||
onChange={(value) => {
|
||||
handleInputChange('openai_organization', value);
|
||||
}}
|
||||
value={inputs.openai_organization}
|
||||
/>
|
||||
{inputs.type === 1 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>组织:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label='组织,可选,不填则为默认组织'
|
||||
name='openai_organization'
|
||||
placeholder='请输入组织org-xxx'
|
||||
onChange={(value) => {
|
||||
handleInputChange('openai_organization', value);
|
||||
}}
|
||||
value={inputs.openai_organization}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>默认测试模型:</Typography.Text>
|
||||
</div>
|
||||
@@ -745,6 +776,50 @@ const EditChannel = (props) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
状态码复写(仅影响本地判断,不修改返回到上游的状态码):
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={`此项可选,用于复写返回的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:\n${JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)}`}
|
||||
name='status_code_mapping'
|
||||
onChange={(value) => {
|
||||
handleInputChange('status_code_mapping', value);
|
||||
}}
|
||||
autosize
|
||||
value={inputs.status_code_mapping}
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
handleInputChange(
|
||||
'status_code_mapping',
|
||||
JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2),
|
||||
);
|
||||
}}
|
||||
>
|
||||
填入模板
|
||||
</Typography.Text>
|
||||
{/*<div style={{ marginTop: 10 }}>*/}
|
||||
{/* <Typography.Text strong>*/}
|
||||
{/* 最大请求token(0表示不限制):*/}
|
||||
{/* </Typography.Text>*/}
|
||||
{/*</div>*/}
|
||||
{/*<Input*/}
|
||||
{/* label='最大请求token'*/}
|
||||
{/* name='max_input_tokens'*/}
|
||||
{/* placeholder='默认为0,表示不限制'*/}
|
||||
{/* onChange={(value) => {*/}
|
||||
{/* handleInputChange('max_input_tokens', value);*/}
|
||||
{/* }}*/}
|
||||
{/* value={inputs.max_input_tokens}*/}
|
||||
{/*/>*/}
|
||||
</Spin>
|
||||
</SideSheet>
|
||||
</>
|
||||
|
||||
@@ -72,6 +72,7 @@ const Detail = (props) => {
|
||||
stack: true,
|
||||
legends: {
|
||||
visible: true,
|
||||
selectMode: 'single',
|
||||
},
|
||||
title: {
|
||||
visible: true,
|
||||
@@ -216,6 +217,8 @@ const Detail = (props) => {
|
||||
} else if (dataExportDefaultTime === 'week') {
|
||||
timeGranularity = 604800;
|
||||
}
|
||||
// sort created_at
|
||||
data.sort((a, b) => a.created_at - b.created_at);
|
||||
data.forEach((item) => {
|
||||
item['created_at'] =
|
||||
Math.floor(item['created_at'] / timeGranularity) * timeGranularity;
|
||||
|
||||
Reference in New Issue
Block a user