mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-26 00:08:38 +00:00
🎨 chore(web): apply ESLint and Prettier auto-fixes (baseline)
- Ran: bun run eslint:fix && bun run lint:fix - Inserted AGPL license header via eslint-plugin-header - Enforced no-multiple-empty-lines and other lint rules - Formatted code using Prettier v3 (@so1ve/prettier-config) - No functional changes; formatting-only baseline across JS/JSX files
This commit is contained in:
@@ -18,14 +18,8 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
SideSheet,
|
||||
Typography,
|
||||
Button,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconClose,
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { SideSheet, Typography, Button } from '@douyinfe/semi-ui';
|
||||
import { IconClose } from '@douyinfe/semi-icons';
|
||||
|
||||
import { useIsMobile } from '../../../../hooks/common/useIsMobile';
|
||||
import ModelHeader from './components/ModelHeader';
|
||||
@@ -54,36 +48,46 @@ const ModelDetailSideSheet = ({
|
||||
|
||||
return (
|
||||
<SideSheet
|
||||
placement="right"
|
||||
title={<ModelHeader modelData={modelData} vendorsMap={vendorsMap} t={t} />}
|
||||
placement='right'
|
||||
title={
|
||||
<ModelHeader modelData={modelData} vendorsMap={vendorsMap} t={t} />
|
||||
}
|
||||
bodyStyle={{
|
||||
padding: '0',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderBottom: '1px solid var(--semi-color-border)'
|
||||
borderBottom: '1px solid var(--semi-color-border)',
|
||||
}}
|
||||
visible={visible}
|
||||
width={isMobile ? '100%' : 600}
|
||||
closeIcon={
|
||||
<Button
|
||||
className="semi-button-tertiary semi-button-size-small semi-button-borderless"
|
||||
type="button"
|
||||
className='semi-button-tertiary semi-button-size-small semi-button-borderless'
|
||||
type='button'
|
||||
icon={<IconClose />}
|
||||
onClick={onClose}
|
||||
/>
|
||||
}
|
||||
onCancel={onClose}
|
||||
>
|
||||
<div className="p-2">
|
||||
<div className='p-2'>
|
||||
{!modelData && (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Text type="secondary">{t('加载中...')}</Text>
|
||||
<div className='flex justify-center items-center py-10'>
|
||||
<Text type='secondary'>{t('加载中...')}</Text>
|
||||
</div>
|
||||
)}
|
||||
{modelData && (
|
||||
<>
|
||||
<ModelBasicInfo modelData={modelData} vendorsMap={vendorsMap} t={t} />
|
||||
<ModelEndpoints modelData={modelData} endpointMap={endpointMap} t={t} />
|
||||
<ModelBasicInfo
|
||||
modelData={modelData}
|
||||
vendorsMap={vendorsMap}
|
||||
t={t}
|
||||
/>
|
||||
<ModelEndpoints
|
||||
modelData={modelData}
|
||||
endpointMap={endpointMap}
|
||||
t={t}
|
||||
/>
|
||||
<ModelPricingTable
|
||||
modelData={modelData}
|
||||
groupRatio={groupRatio}
|
||||
@@ -102,4 +106,4 @@ const ModelDetailSideSheet = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelDetailSideSheet;
|
||||
export default ModelDetailSideSheet;
|
||||
|
||||
@@ -23,12 +23,7 @@ import { resetPricingFilters } from '../../../../helpers/utils';
|
||||
import FilterModalContent from './components/FilterModalContent';
|
||||
import FilterModalFooter from './components/FilterModalFooter';
|
||||
|
||||
const PricingFilterModal = ({
|
||||
visible,
|
||||
onClose,
|
||||
sidebarProps,
|
||||
t
|
||||
}) => {
|
||||
const PricingFilterModal = ({ visible, onClose, sidebarProps, t }) => {
|
||||
const handleResetFilters = () =>
|
||||
resetPricingFilters({
|
||||
handleChange: sidebarProps.handleChange,
|
||||
@@ -46,11 +41,7 @@ const PricingFilterModal = ({
|
||||
});
|
||||
|
||||
const footer = (
|
||||
<FilterModalFooter
|
||||
onReset={handleResetFilters}
|
||||
onConfirm={onClose}
|
||||
t={t}
|
||||
/>
|
||||
<FilterModalFooter onReset={handleResetFilters} onConfirm={onClose} t={t} />
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -65,7 +56,7 @@ const PricingFilterModal = ({
|
||||
height: 'calc(100vh - 160px)',
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
msOverflowStyle: 'none'
|
||||
msOverflowStyle: 'none',
|
||||
}}
|
||||
>
|
||||
<FilterModalContent sidebarProps={sidebarProps} t={t} />
|
||||
@@ -73,4 +64,4 @@ const PricingFilterModal = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default PricingFilterModal;
|
||||
export default PricingFilterModal;
|
||||
|
||||
@@ -135,4 +135,4 @@ const FilterModalContent = ({ sidebarProps, t }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterModalContent;
|
||||
export default FilterModalContent;
|
||||
|
||||
@@ -22,23 +22,15 @@ import { Button } from '@douyinfe/semi-ui';
|
||||
|
||||
const FilterModalFooter = ({ onReset, onConfirm, t }) => {
|
||||
return (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
theme="outline"
|
||||
type='tertiary'
|
||||
onClick={onReset}
|
||||
>
|
||||
<div className='flex justify-end'>
|
||||
<Button theme='outline' type='tertiary' onClick={onReset}>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
<Button
|
||||
theme="solid"
|
||||
type="primary"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
<Button theme='solid' type='primary' onClick={onConfirm}>
|
||||
{t('确定')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterModalFooter;
|
||||
export default FilterModalFooter;
|
||||
|
||||
@@ -47,8 +47,8 @@ const ModelBasicInfo = ({ modelData, vendorsMap = {}, t }) => {
|
||||
const tags = [];
|
||||
|
||||
if (modelData?.tags) {
|
||||
const customTags = modelData.tags.split(',').filter(tag => tag.trim());
|
||||
customTags.forEach(tag => {
|
||||
const customTags = modelData.tags.split(',').filter((tag) => tag.trim());
|
||||
customTags.forEach((tag) => {
|
||||
const tagText = tag.trim();
|
||||
tags.push({ text: tagText, color: stringToColor(tagText) });
|
||||
});
|
||||
@@ -58,27 +58,24 @@ const ModelBasicInfo = ({ modelData, vendorsMap = {}, t }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="!rounded-2xl shadow-sm border-0 mb-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<Avatar size="small" color="blue" className="mr-2 shadow-md">
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
<div className='flex items-center mb-4'>
|
||||
<Avatar size='small' color='blue' className='mr-2 shadow-md'>
|
||||
<IconInfoCircle size={16} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className="text-lg font-medium">{t('基本信息')}</Text>
|
||||
<div className="text-xs text-gray-600">{t('模型的详细描述和基本特性')}</div>
|
||||
<Text className='text-lg font-medium'>{t('基本信息')}</Text>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('模型的详细描述和基本特性')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-gray-600">
|
||||
<p className="mb-4">{getModelDescription()}</p>
|
||||
<div className='text-gray-600'>
|
||||
<p className='mb-4'>{getModelDescription()}</p>
|
||||
{getModelTags().length > 0 && (
|
||||
<Space wrap>
|
||||
{getModelTags().map((tag, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
color={tag.color}
|
||||
shape="circle"
|
||||
size="small"
|
||||
>
|
||||
<Tag key={index} color={tag.color} shape='circle' size='small'>
|
||||
{tag.text}
|
||||
</Tag>
|
||||
))}
|
||||
@@ -89,4 +86,4 @@ const ModelBasicInfo = ({ modelData, vendorsMap = {}, t }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelBasicInfo;
|
||||
export default ModelBasicInfo;
|
||||
|
||||
@@ -30,7 +30,7 @@ const ModelEndpoints = ({ modelData, endpointMap = {}, t }) => {
|
||||
const mapping = endpointMap;
|
||||
const types = modelData.supported_endpoint_types || [];
|
||||
|
||||
return types.map(type => {
|
||||
return types.map((type) => {
|
||||
const info = mapping[type] || {};
|
||||
let path = info.path || '';
|
||||
// 如果路径中包含 {model} 占位符,替换为真实模型名称
|
||||
@@ -42,22 +42,19 @@ const ModelEndpoints = ({ modelData, endpointMap = {}, t }) => {
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
className="flex justify-between border-b border-dashed last:border-0 py-2 last:pb-0"
|
||||
className='flex justify-between border-b border-dashed last:border-0 py-2 last:pb-0'
|
||||
style={{ borderColor: 'var(--semi-color-border)' }}
|
||||
>
|
||||
<span className="flex items-center pr-5">
|
||||
<Badge dot type="success" className="mr-2" />
|
||||
{type}{path && ':'}
|
||||
<span className='flex items-center pr-5'>
|
||||
<Badge dot type='success' className='mr-2' />
|
||||
{type}
|
||||
{path && ':'}
|
||||
{path && (
|
||||
<span className="text-gray-500 md:ml-1 break-all">
|
||||
{path}
|
||||
</span>
|
||||
<span className='text-gray-500 md:ml-1 break-all'>{path}</span>
|
||||
)}
|
||||
</span>
|
||||
{path && (
|
||||
<span className="text-gray-500 text-xs md:ml-1">
|
||||
{method}
|
||||
</span>
|
||||
<span className='text-gray-500 text-xs md:ml-1'>{method}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -65,14 +62,16 @@ const ModelEndpoints = ({ modelData, endpointMap = {}, t }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="!rounded-2xl shadow-sm border-0 mb-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<Avatar size="small" color="purple" className="mr-2 shadow-md">
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
<div className='flex items-center mb-4'>
|
||||
<Avatar size='small' color='purple' className='mr-2 shadow-md'>
|
||||
<IconLink size={16} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className="text-lg font-medium">{t('API端点')}</Text>
|
||||
<div className="text-xs text-gray-600">{t('模型支持的接口端点信息')}</div>
|
||||
<Text className='text-lg font-medium'>{t('API端点')}</Text>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('模型支持的接口端点信息')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{renderAPIEndpoints()}
|
||||
@@ -80,4 +79,4 @@ const ModelEndpoints = ({ modelData, endpointMap = {}, t }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelEndpoints;
|
||||
export default ModelEndpoints;
|
||||
|
||||
@@ -24,8 +24,9 @@ import { getLobeHubIcon } from '../../../../../helpers';
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
const CARD_STYLES = {
|
||||
container: "w-12 h-12 rounded-2xl flex items-center justify-center relative shadow-md",
|
||||
icon: "w-8 h-8 flex items-center justify-center",
|
||||
container:
|
||||
'w-12 h-12 rounded-2xl flex items-center justify-center relative shadow-md',
|
||||
icon: 'w-8 h-8 flex items-center justify-center',
|
||||
};
|
||||
|
||||
const ModelHeader = ({ modelData, vendorsMap = {}, t }) => {
|
||||
@@ -57,13 +58,13 @@ const ModelHeader = ({ modelData, vendorsMap = {}, t }) => {
|
||||
return (
|
||||
<div className={CARD_STYLES.container}>
|
||||
<Avatar
|
||||
size="large"
|
||||
size='large'
|
||||
style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold'
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
{avatarText}
|
||||
@@ -73,21 +74,23 @@ const ModelHeader = ({ modelData, vendorsMap = {}, t }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div className='flex items-center'>
|
||||
{getModelIcon()}
|
||||
<div className="ml-3 font-normal">
|
||||
<div className='ml-3 font-normal'>
|
||||
<Paragraph
|
||||
className="!mb-0 !text-lg !font-medium"
|
||||
className='!mb-0 !text-lg !font-medium'
|
||||
copyable={{
|
||||
content: modelData?.model_name || '',
|
||||
onCopy: () => Toast.success({ content: t('已复制模型名称') })
|
||||
onCopy: () => Toast.success({ content: t('已复制模型名称') }),
|
||||
}}
|
||||
>
|
||||
<span className="truncate max-w-60 font-bold">{modelData?.model_name || t('未知模型')}</span>
|
||||
<span className='truncate max-w-60 font-bold'>
|
||||
{modelData?.model_name || t('未知模型')}
|
||||
</span>
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelHeader;
|
||||
export default ModelHeader;
|
||||
|
||||
@@ -35,37 +35,50 @@ const ModelPricingTable = ({
|
||||
autoGroups = [],
|
||||
t,
|
||||
}) => {
|
||||
const modelEnableGroups = Array.isArray(modelData?.enable_groups) ? modelData.enable_groups : [];
|
||||
const autoChain = autoGroups.filter(g => modelEnableGroups.includes(g));
|
||||
const modelEnableGroups = Array.isArray(modelData?.enable_groups)
|
||||
? modelData.enable_groups
|
||||
: [];
|
||||
const autoChain = autoGroups.filter((g) => modelEnableGroups.includes(g));
|
||||
const renderGroupPriceTable = () => {
|
||||
// 仅展示模型可用的分组:模型 enable_groups 与用户可用分组的交集
|
||||
|
||||
const availableGroups = Object.keys(usableGroup || {})
|
||||
.filter(g => g !== '')
|
||||
.filter(g => g !== 'auto')
|
||||
.filter(g => modelEnableGroups.includes(g));
|
||||
.filter((g) => g !== '')
|
||||
.filter((g) => g !== 'auto')
|
||||
.filter((g) => modelEnableGroups.includes(g));
|
||||
|
||||
// 准备表格数据
|
||||
const tableData = availableGroups.map(group => {
|
||||
const priceData = modelData ? calculateModelPrice({
|
||||
record: modelData,
|
||||
selectedGroup: group,
|
||||
groupRatio,
|
||||
tokenUnit,
|
||||
displayPrice,
|
||||
currency
|
||||
}) : { inputPrice: '-', outputPrice: '-', price: '-' };
|
||||
const tableData = availableGroups.map((group) => {
|
||||
const priceData = modelData
|
||||
? calculateModelPrice({
|
||||
record: modelData,
|
||||
selectedGroup: group,
|
||||
groupRatio,
|
||||
tokenUnit,
|
||||
displayPrice,
|
||||
currency,
|
||||
})
|
||||
: { inputPrice: '-', outputPrice: '-', price: '-' };
|
||||
|
||||
// 获取分组倍率
|
||||
const groupRatioValue = groupRatio && groupRatio[group] ? groupRatio[group] : 1;
|
||||
const groupRatioValue =
|
||||
groupRatio && groupRatio[group] ? groupRatio[group] : 1;
|
||||
|
||||
return {
|
||||
key: group,
|
||||
group: group,
|
||||
ratio: groupRatioValue,
|
||||
billingType: modelData?.quota_type === 0 ? t('按量计费') : (modelData?.quota_type === 1 ? t('按次计费') : '-'),
|
||||
billingType:
|
||||
modelData?.quota_type === 0
|
||||
? t('按量计费')
|
||||
: modelData?.quota_type === 1
|
||||
? t('按次计费')
|
||||
: '-',
|
||||
inputPrice: modelData?.quota_type === 0 ? priceData.inputPrice : '-',
|
||||
outputPrice: modelData?.quota_type === 0 ? (priceData.completionPrice || priceData.outputPrice) : '-',
|
||||
outputPrice:
|
||||
modelData?.quota_type === 0
|
||||
? priceData.completionPrice || priceData.outputPrice
|
||||
: '-',
|
||||
fixedPrice: modelData?.quota_type === 1 ? priceData.price : '-',
|
||||
};
|
||||
});
|
||||
@@ -76,8 +89,9 @@ const ModelPricingTable = ({
|
||||
title: t('分组'),
|
||||
dataIndex: 'group',
|
||||
render: (text) => (
|
||||
<Tag color="white" size="small" shape="circle">
|
||||
{text}{t('分组')}
|
||||
<Tag color='white' size='small' shape='circle'>
|
||||
{text}
|
||||
{t('分组')}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
@@ -89,7 +103,7 @@ const ModelPricingTable = ({
|
||||
title: t('倍率'),
|
||||
dataIndex: 'ratio',
|
||||
render: (text) => (
|
||||
<Tag color="white" size="small" shape="circle">
|
||||
<Tag color='white' size='small' shape='circle'>
|
||||
{text}x
|
||||
</Tag>
|
||||
),
|
||||
@@ -105,7 +119,7 @@ const ModelPricingTable = ({
|
||||
if (text === t('按量计费')) color = 'violet';
|
||||
else if (text === t('按次计费')) color = 'teal';
|
||||
return (
|
||||
<Tag color={color} size="small" shape="circle">
|
||||
<Tag color={color} size='small' shape='circle'>
|
||||
{text || '-'}
|
||||
</Tag>
|
||||
);
|
||||
@@ -121,8 +135,10 @@ const ModelPricingTable = ({
|
||||
dataIndex: 'inputPrice',
|
||||
render: (text) => (
|
||||
<>
|
||||
<div className="font-semibold text-orange-600">{text}</div>
|
||||
<div className="text-xs text-gray-500">/ {tokenUnit === 'K' ? '1K' : '1M'} tokens</div>
|
||||
<div className='font-semibold text-orange-600'>{text}</div>
|
||||
<div className='text-xs text-gray-500'>
|
||||
/ {tokenUnit === 'K' ? '1K' : '1M'} tokens
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -131,11 +147,13 @@ const ModelPricingTable = ({
|
||||
dataIndex: 'outputPrice',
|
||||
render: (text) => (
|
||||
<>
|
||||
<div className="font-semibold text-orange-600">{text}</div>
|
||||
<div className="text-xs text-gray-500">/ {tokenUnit === 'K' ? '1K' : '1M'} tokens</div>
|
||||
<div className='font-semibold text-orange-600'>{text}</div>
|
||||
<div className='text-xs text-gray-500'>
|
||||
/ {tokenUnit === 'K' ? '1K' : '1M'} tokens
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// 按次计费
|
||||
@@ -144,8 +162,8 @@ const ModelPricingTable = ({
|
||||
dataIndex: 'fixedPrice',
|
||||
render: (text) => (
|
||||
<>
|
||||
<div className="font-semibold text-orange-600">{text}</div>
|
||||
<div className="text-xs text-gray-500">/ 次</div>
|
||||
<div className='font-semibold text-orange-600'>{text}</div>
|
||||
<div className='text-xs text-gray-500'>/ 次</div>
|
||||
</>
|
||||
),
|
||||
});
|
||||
@@ -156,32 +174,37 @@ const ModelPricingTable = ({
|
||||
dataSource={tableData}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
size="small"
|
||||
size='small'
|
||||
bordered={false}
|
||||
className="!rounded-lg"
|
||||
className='!rounded-lg'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="!rounded-2xl shadow-sm border-0">
|
||||
<div className="flex items-center mb-4">
|
||||
<Avatar size="small" color="orange" className="mr-2 shadow-md">
|
||||
<Card className='!rounded-2xl shadow-sm border-0'>
|
||||
<div className='flex items-center mb-4'>
|
||||
<Avatar size='small' color='orange' className='mr-2 shadow-md'>
|
||||
<IconCoinMoneyStroked size={16} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className="text-lg font-medium">{t('分组价格')}</Text>
|
||||
<div className="text-xs text-gray-600">{t('不同用户分组的价格信息')}</div>
|
||||
<Text className='text-lg font-medium'>{t('分组价格')}</Text>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('不同用户分组的价格信息')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{autoChain.length > 0 && (
|
||||
<div className="flex flex-wrap items-center gap-1 mb-4">
|
||||
<span className="text-sm text-gray-600">{t('auto分组调用链路')}</span>
|
||||
<span className="text-sm">→</span>
|
||||
<div className='flex flex-wrap items-center gap-1 mb-4'>
|
||||
<span className='text-sm text-gray-600'>{t('auto分组调用链路')}</span>
|
||||
<span className='text-sm'>→</span>
|
||||
{autoChain.map((g, idx) => (
|
||||
<React.Fragment key={g}>
|
||||
<Tag color="white" size="small" shape="circle">{g}{t('分组')}</Tag>
|
||||
{idx < autoChain.length - 1 && <span className="text-sm">→</span>}
|
||||
<Tag color='white' size='small' shape='circle'>
|
||||
{g}
|
||||
{t('分组')}
|
||||
</Tag>
|
||||
{idx < autoChain.length - 1 && <span className='text-sm'>→</span>}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
@@ -191,4 +214,4 @@ const ModelPricingTable = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelPricingTable;
|
||||
export default ModelPricingTable;
|
||||
|
||||
Reference in New Issue
Block a user