import React, { useContext, useEffect, useRef, useMemo, useState } from 'react';
import { API, copy, showError, showInfo, showSuccess } from '../helpers';
import { useTranslation } from 'react-i18next';
import {
Banner,
Input,
Layout,
Modal,
Space,
Table,
Tag,
Tooltip,
Popover,
ImagePreview,
Button,
} from '@douyinfe/semi-ui';
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';
const ModelPricing = () => {
const { t } = useTranslation();
const [filteredValue, setFilteredValue] = useState([]);
const compositionRef = useRef({ isComposition: false });
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [modalImageUrl, setModalImageUrl] = useState('');
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
const [selectedGroup, setSelectedGroup] = useState('default');
const rowSelection = useMemo(
() => ({
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys);
},
}),
[],
);
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);
};
function renderQuotaType(type) {
// Ensure all cases are string literals by adding quotes.
switch (type) {
case 1:
return (
{t('按次计费')}
);
case 0:
return (
{t('按量计费')}
);
default:
return t('未知');
}
}
function renderAvailable(available) {
return available ? (
{t('您的分组可以使用该模型')}
}
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',
}}
>
) : null;
}
const columns = [
{
title: t('可用性'),
dataIndex: 'available',
render: (text, record, index) => {
// if record.enable_groups contains selectedGroup, then available is true
return renderAvailable(record.enable_groups.includes(selectedGroup));
},
sorter: (a, b) => {
const aAvailable = a.enable_groups.includes(selectedGroup);
const bAvailable = b.enable_groups.includes(selectedGroup);
return Number(aAvailable) - Number(bAvailable);
},
defaultSortOrder: 'descend',
},
{
title: t('模型名称'),
dataIndex: 'model_name',
render: (text, record, index) => {
return (
<>
{
copyText(text);
}}
>
{text}
>
);
},
onFilter: (value, record) =>
record.model_name.toLowerCase().includes(value.toLowerCase()),
filteredValue,
},
{
title: t('计费类型'),
dataIndex: 'quota_type',
render: (text, record, index) => {
return renderQuotaType(parseInt(text));
},
sorter: (a, b) => a.quota_type - b.quota_type,
},
{
title: t('可用分组'),
dataIndex: 'enable_groups',
render: (text, record, index) => {
// enable_groups is a string array
return (
{text.map((group) => {
if (usableGroup[group]) {
if (group === selectedGroup) {
return (
}>
{group}
);
} else {
return (
{
setSelectedGroup(group);
showInfo(
t('当前查看的分组为:{{group}},倍率为:{{ratio}}', {
group: group,
ratio: groupRatio[group],
}),
);
}}
>
{group}
);
}
}
})}
);
},
},
{
title: () => (
{t('倍率')}
{t('倍率是为了方便换算不同价格的模型')}
{t('点击查看倍率说明')}
}
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',
}}
>
{
setModalImageUrl('/ratio.png');
setIsModalOpenurl(true);
}}
/>
),
dataIndex: 'model_ratio',
render: (text, record, index) => {
let content = text;
let completionRatio = parseFloat(record.completion_ratio.toFixed(3));
content = (
<>
{t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
{t('补全倍率')}:
{record.quota_type === 0 ? completionRatio : t('无')}
{t('分组倍率')}:{groupRatio[selectedGroup]}
>
);
return
{content}
;
},
},
{
title: t('模型价格'),
dataIndex: 'model_price',
render: (text, record, index) => {
let content = text;
if (record.quota_type === 0) {
// 这里的 *2 是因为 1倍率=0.002刀,请勿删除
let inputRatioPrice =
record.model_ratio * 2 * groupRatio[selectedGroup];
let completionRatioPrice =
record.model_ratio *
record.completion_ratio *
2 *
groupRatio[selectedGroup];
content = (
<>
{t('提示')} ${inputRatioPrice} / 1M tokens
{t('补全')} ${completionRatioPrice} / 1M tokens
>
);
} else {
let price = parseFloat(text) * groupRatio[selectedGroup];
content = (
<>
${t('模型价格')}:${price}
>
);
}
return {content}
;
},
},
];
const [models, setModels] = useState([]);
const [loading, setLoading] = useState(true);
const [userState, userDispatch] = useContext(UserContext);
const [groupRatio, setGroupRatio] = useState({});
const [usableGroup, setUsableGroup] = useState({});
const setModelsFormat = (models, groupRatio) => {
for (let i = 0; i < models.length; i++) {
models[i].key = models[i].model_name;
models[i].group_ratio = groupRatio[models[i].model_name];
}
// sort by quota_type
models.sort((a, b) => {
return a.quota_type - b.quota_type;
});
// sort by model_name, start with gpt is max, other use localeCompare
models.sort((a, b) => {
if (a.model_name.startsWith('gpt') && !b.model_name.startsWith('gpt')) {
return -1;
} else if (
!a.model_name.startsWith('gpt') &&
b.model_name.startsWith('gpt')
) {
return 1;
} else {
return a.model_name.localeCompare(b.model_name);
}
});
setModels(models);
};
const loadPricing = async () => {
setLoading(true);
let url = '';
url = `/api/pricing`;
const res = await API.get(url);
const { success, message, data, group_ratio, usable_group } = res.data;
if (success) {
setGroupRatio(group_ratio);
setUsableGroup(usable_group);
setSelectedGroup(userState.user ? userState.user.group : 'default');
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 (
<>
{userState.user ? (
) : (
)}
{t(
'按量计费费用 = 分组倍率 × 模型倍率 × (提示token数 + 补全token数 × 补全倍率)/ 500000 (单位:美元)',
)}
}
closeIcon='null'
/>
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
start: page.currentStart,
end: page.currentEnd,
total: models.length,
}),
pageSize: models.length,
showSizeChanger: false,
}}
rowSelection={rowSelection}
/>
setIsModalOpenurl(visible)}
/>
>
);
};
export default ModelPricing;