mirror of
https://github.com/linux-do/new-api.git
synced 2025-11-12 09:03:42 +08:00
merge upstream
Signed-off-by: wozulong <>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { getFooterHTML, getSystemName } from '../helpers';
|
||||
import { Layout } from '@douyinfe/semi-ui';
|
||||
import { Layout, Tooltip } from '@douyinfe/semi-ui';
|
||||
|
||||
const Footer = () => {
|
||||
const systemName = getSystemName();
|
||||
@@ -15,6 +15,30 @@ const Footer = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const defaultFooter = (
|
||||
<div className='custom-footer'>
|
||||
<a
|
||||
href='https://github.com/Calcium-Ion/new-api'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
New API {import.meta.env.VITE_REACT_APP_VERSION}{' '}
|
||||
</a>
|
||||
由{' '}
|
||||
<a href='https://github.com/Calcium-Ion' target='_blank' rel='noreferrer'>
|
||||
Calcium-Ion
|
||||
</a>{' '}
|
||||
开发,基于{' '}
|
||||
<a
|
||||
href='https://github.com/songquanpeng/one-api'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
One API
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
if (remainCheckTimes <= 0) {
|
||||
@@ -31,41 +55,14 @@ const Footer = () => {
|
||||
<Layout>
|
||||
<Layout.Content style={{ textAlign: 'center' }}>
|
||||
{footer ? (
|
||||
<div
|
||||
className='custom-footer'
|
||||
dangerouslySetInnerHTML={{ __html: footer }}
|
||||
></div>
|
||||
<Tooltip content={defaultFooter}>
|
||||
<div
|
||||
className='custom-footer'
|
||||
dangerouslySetInnerHTML={{ __html: footer }}
|
||||
></div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div className='custom-footer'>
|
||||
<a
|
||||
href='https://github.com/Calcium-Ion/new-api'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
New API {import.meta.env.VITE_REACT_APP_VERSION}{' '}
|
||||
</a>
|
||||
由{' '}
|
||||
<a
|
||||
href='https://github.com/Calcium-Ion'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Calcium-Ion
|
||||
</a>{' '}
|
||||
开发,基于{' '}
|
||||
<a
|
||||
href='https://github.com/songquanpeng/one-api'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
One API v0.5.4
|
||||
</a>{' '}
|
||||
,本项目根据{' '}
|
||||
<a href='https://opensource.org/licenses/mit-license.php'>
|
||||
MIT 许可证
|
||||
</a>{' '}
|
||||
授权
|
||||
</div>
|
||||
defaultFooter
|
||||
)}
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { useContext, useEffect, useRef, useMemo, useState } from 'react';
|
||||
import { API, copy, showError, showSuccess } from '../helpers';
|
||||
|
||||
import {
|
||||
@@ -10,8 +10,16 @@ import {
|
||||
Table,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Popover,
|
||||
ImagePreview,
|
||||
Button,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { stringToColor } from '../helpers/render.js';
|
||||
import {
|
||||
IconMore,
|
||||
IconVerify,
|
||||
IconUploadError,
|
||||
IconHelpCircle,
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { UserContext } from '../context/User/index.js';
|
||||
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
|
||||
|
||||
@@ -20,42 +28,70 @@ function renderQuotaType(type) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return (
|
||||
<Tag color='green' size='large'>
|
||||
<Tag color='teal' size='large'>
|
||||
按次计费
|
||||
</Tag>
|
||||
);
|
||||
case 0:
|
||||
return (
|
||||
<Tag color='blue' size='large'>
|
||||
<Tag color='violet' size='large'>
|
||||
按量计费
|
||||
</Tag>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Tag color='white' size='large'>
|
||||
未知
|
||||
</Tag>
|
||||
);
|
||||
return '未知';
|
||||
}
|
||||
}
|
||||
|
||||
function renderAvailable(available) {
|
||||
return available ? (
|
||||
<Tag color='green' size='large'>
|
||||
可用
|
||||
</Tag>
|
||||
<Popover
|
||||
content={<div style={{ padding: 8 }}>您的分组可以使用该模型</div>}
|
||||
position='top'
|
||||
key={available}
|
||||
style={{
|
||||
backgroundColor: 'rgba(var(--semi-blue-4),1)',
|
||||
borderColor: 'rgba(var(--semi-blue-4),1)',
|
||||
color: 'var(--semi-color-white)',
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
}}
|
||||
>
|
||||
<IconVerify style={{ color: 'green' }} size='large' />
|
||||
</Popover>
|
||||
) : (
|
||||
<Tooltip content='您所在的分组不可用'>
|
||||
<Tag color='red' size='large'>
|
||||
不可用
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
<Popover
|
||||
content={<div style={{ padding: 8 }}>您的分组无权使用该模型</div>}
|
||||
position='top'
|
||||
key={available}
|
||||
style={{
|
||||
backgroundColor: 'rgba(var(--semi-blue-4),1)',
|
||||
borderColor: 'rgba(var(--semi-blue-4),1)',
|
||||
color: 'var(--semi-color-white)',
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
}}
|
||||
>
|
||||
<IconUploadError style={{ color: '#FFA54F' }} size='large' />
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
const ModelPricing = () => {
|
||||
const [filteredValue, setFilteredValue] = useState([]);
|
||||
const compositionRef = useRef({ isComposition: false });
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
||||
const [modalImageUrl, setModalImageUrl] = useState('');
|
||||
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
|
||||
|
||||
const rowSelection = useMemo(
|
||||
() => ({
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
setSelectedRowKeys(selectedRowKeys);
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const handleChange = (value) => {
|
||||
if (compositionRef.current.isComposition) {
|
||||
@@ -103,7 +139,7 @@ const ModelPricing = () => {
|
||||
return (
|
||||
<>
|
||||
<Tag
|
||||
color={stringToColor(text)}
|
||||
color='green'
|
||||
size='large'
|
||||
onClick={() => {
|
||||
copyText(text);
|
||||
@@ -114,7 +150,8 @@ const ModelPricing = () => {
|
||||
</>
|
||||
);
|
||||
},
|
||||
onFilter: (value, record) => record.model_name.includes(value),
|
||||
onFilter: (value, record) =>
|
||||
record.model_name.toLowerCase().includes(value.toLowerCase()),
|
||||
filteredValue,
|
||||
},
|
||||
{
|
||||
@@ -126,18 +163,49 @@ const ModelPricing = () => {
|
||||
sorter: (a, b) => a.quota_type - b.quota_type,
|
||||
},
|
||||
{
|
||||
title: '模型倍率',
|
||||
title: () => (
|
||||
<span style={{ display: 'flex', alignItems: 'center' }}>
|
||||
倍率
|
||||
<Popover
|
||||
content={
|
||||
<div style={{ padding: 8 }}>
|
||||
倍率是为了方便换算不同价格的模型
|
||||
<br />
|
||||
点击查看倍率说明
|
||||
</div>
|
||||
}
|
||||
position='top'
|
||||
style={{
|
||||
backgroundColor: 'rgba(var(--semi-blue-4),1)',
|
||||
borderColor: 'rgba(var(--semi-blue-4),1)',
|
||||
color: 'var(--semi-color-white)',
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
}}
|
||||
>
|
||||
<IconHelpCircle
|
||||
onClick={() => {
|
||||
setModalImageUrl('/ratio.png');
|
||||
setIsModalOpenurl(true);
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
</span>
|
||||
),
|
||||
dataIndex: 'model_ratio',
|
||||
render: (text, record, index) => {
|
||||
return <div>{record.quota_type === 0 ? text : 'N/A'}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '补全倍率',
|
||||
dataIndex: 'completion_ratio',
|
||||
render: (text, record, index) => {
|
||||
let ratio = parseFloat(text.toFixed(3));
|
||||
return <div>{record.quota_type === 0 ? ratio : 'N/A'}</div>;
|
||||
let content = text;
|
||||
let completionRatio = parseFloat(record.completion_ratio.toFixed(3));
|
||||
content = (
|
||||
<>
|
||||
<Text>模型:{record.quota_type === 0 ? text : '无'}</Text>
|
||||
<br />
|
||||
<Text>
|
||||
补全:{record.quota_type === 0 ? completionRatio : '无'}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
return <div>{content}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -176,7 +244,7 @@ const ModelPricing = () => {
|
||||
|
||||
const setModelsFormat = (models, groupRatio) => {
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
models[i].key = i;
|
||||
models[i].key = models[i].model_name;
|
||||
models[i].group_ratio = groupRatio;
|
||||
}
|
||||
// sort by quota_type
|
||||
@@ -239,15 +307,43 @@ const ModelPricing = () => {
|
||||
<Layout>
|
||||
{userState.user ? (
|
||||
<Banner
|
||||
type='info'
|
||||
type='success'
|
||||
fullMode={false}
|
||||
closeIcon='null'
|
||||
description={`您的分组为:${userState.user.group},分组倍率为:${groupRatio}`}
|
||||
/>
|
||||
) : (
|
||||
<Banner
|
||||
type='warning'
|
||||
fullMode={false}
|
||||
closeIcon='null'
|
||||
description={`您还未登陆,显示的价格为默认分组倍率: ${groupRatio}`}
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
<Banner
|
||||
type='info'
|
||||
fullMode={false}
|
||||
description={
|
||||
<div>
|
||||
按量计费费用 = 分组倍率 × 模型倍率 × (提示token数 + 补全token数 ×
|
||||
补全倍率)/ 500000 (单位:美元)
|
||||
</div>
|
||||
}
|
||||
closeIcon='null'
|
||||
/>
|
||||
<br />
|
||||
<Button
|
||||
theme='light'
|
||||
type='tertiary'
|
||||
style={{ width: 150 }}
|
||||
onClick={() => {
|
||||
copyText(selectedRowKeys);
|
||||
}}
|
||||
disabled={selectedRowKeys == ''}
|
||||
>
|
||||
复制选中模型
|
||||
</Button>
|
||||
<Table
|
||||
style={{ marginTop: 5 }}
|
||||
columns={columns}
|
||||
@@ -257,6 +353,12 @@ const ModelPricing = () => {
|
||||
pageSize: models.length,
|
||||
showSizeChanger: false,
|
||||
}}
|
||||
rowSelection={rowSelection}
|
||||
/>
|
||||
<ImagePreview
|
||||
src={modalImageUrl}
|
||||
visible={isModalOpenurl}
|
||||
onVisibleChange={(visible) => setIsModalOpenurl(visible)}
|
||||
/>
|
||||
</Layout>
|
||||
</>
|
||||
|
||||
@@ -216,6 +216,15 @@ export const verifyJSON = (str) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export function verifyJSONPromise(value) {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return Promise.resolve();
|
||||
} catch (e) {
|
||||
return Promise.reject('不是合法的 JSON 字符串');
|
||||
}
|
||||
}
|
||||
|
||||
export function shouldShowPrompt(id) {
|
||||
let prompt = localStorage.getItem(`prompt-${id}`);
|
||||
return !prompt;
|
||||
|
||||
@@ -39,13 +39,16 @@ const About = () => {
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<p>可在设置页面设置关于内容,支持 HTML & Markdown</p>
|
||||
new-api项目仓库地址:
|
||||
New-API项目仓库地址:
|
||||
<a href='https://github.com/Calcium-Ion/new-api'>
|
||||
https://github.com/Calcium-Ion/new-api
|
||||
</a>
|
||||
<p>
|
||||
NewAPI © 2023 CalciumIon | 基于 One API v0.5.4 © 2023
|
||||
JustSong。本项目根据MIT许可证授权。
|
||||
JustSong。
|
||||
</p>
|
||||
<p>
|
||||
本项目根据MIT许可证授权,需在遵守Apache-2.0协议的前提下使用。
|
||||
</p>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Space,
|
||||
Spin,
|
||||
Button,
|
||||
Tooltip,
|
||||
Input,
|
||||
Typography,
|
||||
Select,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { Divider } from 'semantic-ui-react';
|
||||
import { getChannelModels, loadChannelModels } from '../../components/utils.js';
|
||||
import axios from 'axios';
|
||||
|
||||
const MODEL_MAPPING_EXAMPLE = {
|
||||
'gpt-3.5-turbo-0301': 'gpt-3.5-turbo',
|
||||
@@ -35,6 +37,9 @@ const STATUS_CODE_MAPPING_EXAMPLE = {
|
||||
400: '500',
|
||||
};
|
||||
|
||||
const fetchButtonTips =
|
||||
'1. 新建渠道时,请求通过当前浏览器发出;2. 编辑已有渠道,请求通过后端服务器发出';
|
||||
|
||||
function type2secretPrompt(type) {
|
||||
// inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')
|
||||
switch (type) {
|
||||
@@ -173,12 +178,58 @@ const EditChannel = (props) => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const fetchUpstreamModelList = async (name) => {
|
||||
if (inputs['type'] !== 1) {
|
||||
showError('仅支持 OpenAI 接口格式');
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const models = inputs['models'] || [];
|
||||
let err = false;
|
||||
if (isEdit) {
|
||||
const res = await API.get('/api/channel/fetch_models/' + channelId);
|
||||
if (res.data && res.data?.success) {
|
||||
models.push(...res.data.data);
|
||||
} else {
|
||||
err = true;
|
||||
}
|
||||
} else {
|
||||
if (!inputs?.['key']) {
|
||||
showError('请填写密钥');
|
||||
err = true;
|
||||
} else {
|
||||
try {
|
||||
const host = new URL(inputs['base_url'] || 'https://api.openai.com');
|
||||
|
||||
const url = `https://${host.hostname}/v1/models`;
|
||||
const key = inputs['key'];
|
||||
const res = await axios.get(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${key}`,
|
||||
},
|
||||
});
|
||||
if (res.data && res.data?.success) {
|
||||
models.push(...es.data.data.map((model) => model.id));
|
||||
} else {
|
||||
err = true;
|
||||
}
|
||||
} catch (error) {
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!err) {
|
||||
handleInputChange(name, Array.from(new Set(models)));
|
||||
showSuccess('获取模型列表成功');
|
||||
} else {
|
||||
showError('获取模型列表失败');
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const fetchModels = async () => {
|
||||
try {
|
||||
let res = await API.get(`/api/channel/models`);
|
||||
if (res === undefined) {
|
||||
return;
|
||||
}
|
||||
let localModelOptions = res.data.data.map((model) => ({
|
||||
label: model.id,
|
||||
value: model.id,
|
||||
@@ -432,6 +483,17 @@ const EditChannel = (props) => {
|
||||
)}
|
||||
{inputs.type === 8 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Banner
|
||||
type={'warning'}
|
||||
description={
|
||||
<>
|
||||
如果你对接的是上游One API或者New
|
||||
API等转发项目,请使用OpenAI类型,不要使用此类型,除非你知道你在做什么。
|
||||
</>
|
||||
}
|
||||
></Banner>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
完整的 Base URL,支持变量{'{model}'}:
|
||||
@@ -550,6 +612,16 @@ const EditChannel = (props) => {
|
||||
>
|
||||
填入所有模型
|
||||
</Button>
|
||||
<Tooltip content={fetchButtonTips}>
|
||||
<Button
|
||||
type='tertiary'
|
||||
onClick={() => {
|
||||
fetchUpstreamModelList('models');
|
||||
}}
|
||||
>
|
||||
获取模型列表
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
type='warning'
|
||||
onClick={() => {
|
||||
|
||||
@@ -86,11 +86,21 @@ const Home = () => {
|
||||
<p>
|
||||
源码:
|
||||
<a
|
||||
href='https://github.com/songquanpeng/one-api'
|
||||
href='https://github.com/Calcium-Ion/new-api'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
https://github.com/songquanpeng/one-api
|
||||
https://github.com/Calcium-Ion/new-api
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
协议:
|
||||
<a
|
||||
href='https://www.apache.org/licenses/LICENSE-2.0'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Apache-2.0 License
|
||||
</a>
|
||||
</p>
|
||||
<p>启动时间:{getStartTimeString()}</p>
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Form,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Space,
|
||||
Spin,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
compareObjects,
|
||||
API,
|
||||
@@ -7,6 +15,7 @@ import {
|
||||
showSuccess,
|
||||
showWarning,
|
||||
verifyJSON,
|
||||
verifyJSONPromise,
|
||||
} from '../../../helpers';
|
||||
|
||||
export default function SettingsMagnification(props) {
|
||||
@@ -22,43 +31,66 @@ export default function SettingsMagnification(props) {
|
||||
|
||||
async function onSubmit() {
|
||||
try {
|
||||
await refForm.current.validate();
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
value = String(inputs[item.key]);
|
||||
} else {
|
||||
value = inputs[item.key];
|
||||
}
|
||||
return API.put('/api/option/', {
|
||||
key: item.key,
|
||||
value,
|
||||
});
|
||||
});
|
||||
setLoading(true);
|
||||
Promise.all(requestQueue)
|
||||
.then((res) => {
|
||||
if (requestQueue.length === 1) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined))
|
||||
return showError('部分保存失败,请重试');
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
props.refresh();
|
||||
console.log('Starting validation...');
|
||||
await refForm.current
|
||||
.validate()
|
||||
.then(() => {
|
||||
console.log('Validation passed');
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
if (!updateArray.length) return showWarning('你似乎并没有修改什么');
|
||||
const requestQueue = updateArray.map((item) => {
|
||||
let value = '';
|
||||
if (typeof inputs[item.key] === 'boolean') {
|
||||
value = String(inputs[item.key]);
|
||||
} else {
|
||||
value = inputs[item.key];
|
||||
}
|
||||
return API.put('/api/option/', {
|
||||
key: item.key,
|
||||
value,
|
||||
});
|
||||
});
|
||||
setLoading(true);
|
||||
Promise.all(requestQueue)
|
||||
.then((res) => {
|
||||
if (requestQueue.length === 1) {
|
||||
if (res.includes(undefined)) return;
|
||||
} else if (requestQueue.length > 1) {
|
||||
if (res.includes(undefined))
|
||||
return showError('部分保存失败,请重试');
|
||||
}
|
||||
showSuccess('保存成功');
|
||||
props.refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
showError('保存失败,请重试');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
.catch((error) => {
|
||||
console.error('Validation failed:', error);
|
||||
showError('请检查输入');
|
||||
});
|
||||
} catch (error) {
|
||||
showError('请检查输入');
|
||||
console.error(error);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
async function resetModelRatio() {
|
||||
try {
|
||||
let res = await API.post(`/api/option/rest_model_ratio`);
|
||||
// return {success, message}
|
||||
if (res.data.success) {
|
||||
showSuccess(res.data.message);
|
||||
props.refresh();
|
||||
} else {
|
||||
showError(res.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,122 +105,141 @@ export default function SettingsMagnification(props) {
|
||||
setInputsRow(structuredClone(currentInputs));
|
||||
refForm.current.setValues(currentInputs);
|
||||
}, [props.options]);
|
||||
return (
|
||||
<>
|
||||
<Spin spinning={loading}>
|
||||
<Form
|
||||
values={inputs}
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'倍率设置'}>
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<Form.TextArea
|
||||
label={'模型固定价格'}
|
||||
extraText={'一次调用消耗多少刀,优先级大于模型倍率'}
|
||||
placeholder={
|
||||
'为一个 JSON 文本,键为模型名称,值为一次调用消耗多少刀,比如 "gpt-4-gizmo-*": 0.1,一次消耗0.1刀'
|
||||
}
|
||||
field={'ModelPrice'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => verifyJSON(value),
|
||||
message: '不是合法的 JSON 字符串',
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
ModelPrice: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<Form.TextArea
|
||||
label={'模型倍率'}
|
||||
extraText={''}
|
||||
placeholder={'为一个 JSON 文本,键为模型名称,值为倍率'}
|
||||
field={'ModelRatio'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => verifyJSON(value),
|
||||
message: '不是合法的 JSON 字符串',
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
ModelRatio: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<Form.TextArea
|
||||
label={'模型补全倍率(仅对自定义模型有效)'}
|
||||
extraText={'仅对自定义模型有效'}
|
||||
placeholder={'为一个 JSON 文本,键为模型名称,值为倍率'}
|
||||
field={'CompletionRatio'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => verifyJSON(value),
|
||||
message: '不是合法的 JSON 字符串',
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
CompletionRatio: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<Form.TextArea
|
||||
label={'分组倍率'}
|
||||
extraText={''}
|
||||
placeholder={'为一个 JSON 文本,键为分组名称,值为倍率'}
|
||||
field={'GroupRatio'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => verifyJSON(value),
|
||||
message: '不是合法的 JSON 字符串',
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
GroupRatio: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Button size='large' onClick={onSubmit}>
|
||||
保存倍率设置
|
||||
</Button>
|
||||
</Row>
|
||||
</Form.Section>
|
||||
</Form>
|
||||
</Spin>
|
||||
</>
|
||||
return (
|
||||
<Spin spinning={loading}>
|
||||
<Form
|
||||
values={inputs}
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={'倍率设置'}>
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<Form.TextArea
|
||||
label={'模型固定价格'}
|
||||
extraText={'一次调用消耗多少刀,优先级大于模型倍率'}
|
||||
placeholder={
|
||||
'为一个 JSON 文本,键为模型名称,值为一次调用消耗多少刀,比如 "gpt-4-gizmo-*": 0.1,一次消耗0.1刀'
|
||||
}
|
||||
field={'ModelPrice'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
stopValidateWithError
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
return verifyJSON(value);
|
||||
},
|
||||
message: '不是合法的 JSON 字符串',
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
ModelPrice: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<Form.TextArea
|
||||
label={'模型倍率'}
|
||||
extraText={''}
|
||||
placeholder={'为一个 JSON 文本,键为模型名称,值为倍率'}
|
||||
field={'ModelRatio'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
stopValidateWithError
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
return verifyJSON(value);
|
||||
},
|
||||
message: '不是合法的 JSON 字符串',
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
ModelRatio: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<Form.TextArea
|
||||
label={'模型补全倍率(仅对自定义模型有效)'}
|
||||
extraText={'仅对自定义模型有效'}
|
||||
placeholder={'为一个 JSON 文本,键为模型名称,值为倍率'}
|
||||
field={'CompletionRatio'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
stopValidateWithError
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
return verifyJSON(value);
|
||||
},
|
||||
message: '不是合法的 JSON 字符串',
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
CompletionRatio: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<Form.TextArea
|
||||
label={'分组倍率'}
|
||||
extraText={''}
|
||||
placeholder={'为一个 JSON 文本,键为分组名称,值为倍率'}
|
||||
field={'GroupRatio'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
stopValidateWithError
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
return verifyJSON(value);
|
||||
},
|
||||
message: '不是合法的 JSON 字符串',
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
GroupRatio: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Section>
|
||||
</Form>
|
||||
<Space>
|
||||
<Button onClick={onSubmit}>保存倍率设置</Button>
|
||||
<Popconfirm
|
||||
title='确定重置模型倍率吗?'
|
||||
content='此修改将不可逆'
|
||||
okType={'danger'}
|
||||
position={'top'}
|
||||
onConfirm={() => {
|
||||
resetModelRatio();
|
||||
}}
|
||||
>
|
||||
<Button type={'danger'}>重置模型倍率</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user