Files
new-api/web/src/components/ModelPricing.js

361 lines
9.6 KiB
JavaScript
Raw Normal View History

2024-05-16 00:35:27 +08:00
import React, { useContext, useEffect, useRef, useMemo, useState } from 'react';
2024-05-13 23:02:35 +08:00
import { API, copy, showError, showSuccess } from '../helpers';
2024-05-15 20:17:27 +08:00
import {
Banner,
Input,
Layout,
Modal,
Space,
Table,
Tag,
Tooltip,
2024-05-16 00:35:27 +08:00
Popover,
ImagePreview,
Button,
2024-05-15 20:17:27 +08:00
} from '@douyinfe/semi-ui';
2024-05-16 00:35:27 +08:00
import {
IconMore,
IconVerify,
IconUploadError,
IconHelpCircle,
} from '@douyinfe/semi-icons';
2024-05-13 23:02:35 +08:00
import { UserContext } from '../context/User/index.js';
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
function renderQuotaType(type) {
// Ensure all cases are string literals by adding quotes.
switch (type) {
case 1:
return (
2024-05-16 00:35:27 +08:00
<Tag color='teal' size='large'>
2024-05-13 23:02:35 +08:00
按次计费
</Tag>
);
case 0:
return (
2024-05-16 00:35:27 +08:00
<Tag color='violet' size='large'>
2024-05-13 23:02:35 +08:00
按量计费
</Tag>
);
default:
2024-05-16 00:35:27 +08:00
return '未知';
2024-05-13 23:02:35 +08:00
}
}
function renderAvailable(available) {
return available ? (
2024-05-16 00:35:27 +08:00
<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>
2024-05-13 23:02:35 +08:00
) : (
2024-05-16 00:35:27 +08:00
<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>
2024-05-13 23:02:35 +08:00
);
}
const ModelPricing = () => {
2024-05-15 20:17:27 +08:00
const [filteredValue, setFilteredValue] = useState([]);
const compositionRef = useRef({ isComposition: false });
2024-05-16 00:35:27 +08:00
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [modalImageUrl, setModalImageUrl] = useState('');
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
const rowSelection = useMemo(
() => ({
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys);
},
}),
[]
);
2024-05-15 20:17:27 +08:00
const handleChange = (value) => {
if (compositionRef.current.isComposition) {
return;
}
const newFilteredValue = value ? [value] : [];
setFilteredValue(newFilteredValue);
};
const handleCompositionStart = () => {
compositionRef.current.isComposition = true;
};
const handleCompositionEnd = (event) => {
compositionRef.current.isComposition = false;
const value = event.target.value;
const newFilteredValue = value ? [value] : [];
setFilteredValue(newFilteredValue);
};
2024-05-13 23:02:35 +08:00
const columns = [
{
title: '可用性',
dataIndex: 'available',
render: (text, record, index) => {
return renderAvailable(text);
},
2024-05-15 20:17:27 +08:00
sorter: (a, b) => a.available - b.available,
2024-05-13 23:02:35 +08:00
},
{
2024-05-15 20:17:27 +08:00
title: (
<Space>
<span>模型名称</span>
<Input
placeholder='模糊搜索'
style={{ width: 200 }}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
onChange={handleChange}
showClear
/>
</Space>
),
2024-05-13 23:02:35 +08:00
dataIndex: 'model_name', // 以finish_time作为dataIndex
render: (text, record, index) => {
return (
<>
<Tag
2024-05-16 00:35:27 +08:00
color='green'
2024-05-13 23:02:35 +08:00
size='large'
onClick={() => {
copyText(text);
}}
>
{text}
</Tag>
</>
);
},
onFilter: (value, record) =>
record.name.toLowerCase().includes(value.toLowerCase()),
2024-05-15 20:17:27 +08:00
filteredValue,
2024-05-13 23:02:35 +08:00
},
{
title: '计费类型',
dataIndex: 'quota_type',
render: (text, record, index) => {
return renderQuotaType(parseInt(text));
},
2024-05-15 20:17:27 +08:00
sorter: (a, b) => a.quota_type - b.quota_type,
2024-05-13 23:02:35 +08:00
},
{
2024-05-16 00:35:27 +08:00
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>
),
2024-05-13 23:02:35 +08:00
dataIndex: 'model_ratio',
render: (text, record, index) => {
2024-05-16 00:35:27 +08:00
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>;
2024-05-13 23:02:35 +08:00
},
},
{
title: '模型价格',
dataIndex: 'model_price',
render: (text, record, index) => {
let content = text;
if (record.quota_type === 0) {
let inputRatioPrice = record.model_ratio * 2.0 * record.group_ratio;
let completionRatioPrice =
record.model_ratio *
record.completion_ratio *
2.0 *
record.group_ratio;
content = (
<>
<Text>提示 ${inputRatioPrice} / 1M tokens</Text>
<br />
<Text>补全 ${completionRatioPrice} / 1M tokens</Text>
</>
);
} else {
let price = parseFloat(text) * record.group_ratio;
content = <>模型价格${price}</>;
}
return <div>{content}</div>;
},
},
];
const [models, setModels] = useState([]);
const [loading, setLoading] = useState(true);
const [userState, userDispatch] = useContext(UserContext);
const [groupRatio, setGroupRatio] = useState(1);
const setModelsFormat = (models, groupRatio) => {
for (let i = 0; i < models.length; i++) {
2024-05-16 00:35:27 +08:00
models[i].key = models[i].model_name;
2024-05-13 23:02:35 +08:00
models[i].group_ratio = groupRatio;
}
// sort by quota_type
models.sort((a, b) => {
return a.quota_type - b.quota_type;
});
2024-05-15 20:17:27 +08:00
// sort by model_name, start with gpt is max, other use localeCompare
2024-05-13 23:02:35 +08:00
models.sort((a, b) => {
2024-05-15 20:17:27 +08:00
if (a.model_name.startsWith('gpt') && !b.model_name.startsWith('gpt')) {
2024-05-13 23:02:35 +08:00
return -1;
2024-05-15 20:17:27 +08:00
} else if (
!a.model_name.startsWith('gpt') &&
b.model_name.startsWith('gpt')
) {
2024-05-13 23:02:35 +08:00
return 1;
} else {
2024-05-15 20:17:27 +08:00
return a.model_name.localeCompare(b.model_name);
2024-05-13 23:02:35 +08:00
}
});
setModels(models);
};
const loadPricing = async () => {
setLoading(true);
let url = '';
url = `/api/pricing`;
const res = await API.get(url);
const { success, message, data, group_ratio } = res.data;
if (success) {
setGroupRatio(group_ratio);
setModelsFormat(data, group_ratio);
} else {
showError(message);
}
setLoading(false);
};
const refresh = async () => {
await loadPricing();
};
const copyText = async (text) => {
if (await copy(text)) {
showSuccess('已复制:' + text);
} else {
// setSearchKeyword(text);
Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
}
};
useEffect(() => {
refresh().then();
}, []);
return (
<>
<Layout>
{userState.user ? (
<Banner
2024-05-16 00:35:27 +08:00
type="success"
fullMode={false}
closeIcon="null"
2024-05-13 23:02:35 +08:00
description={`您的分组为:${userState.user.group},分组倍率为:${groupRatio}`}
/>
) : (
<Banner
type='warning'
2024-05-16 00:35:27 +08:00
fullMode={false}
closeIcon="null"
2024-05-13 23:02:35 +08:00
description={`您还未登陆,显示的价格为默认分组倍率: ${groupRatio}`}
/>
)}
2024-05-16 00:35:27 +08:00
<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>
2024-05-13 23:02:35 +08:00
<Table
style={{ marginTop: 5 }}
columns={columns}
dataSource={models}
loading={loading}
pagination={{
pageSize: models.length,
showSizeChanger: false,
}}
2024-05-16 00:35:27 +08:00
rowSelection={rowSelection}
/>
<ImagePreview
src={modalImageUrl}
visible={isModalOpenurl}
onVisibleChange={(visible) => setIsModalOpenurl(visible)}
2024-05-13 23:02:35 +08:00
/>
</Layout>
</>
);
};
export default ModelPricing;