refactor: streamline model card to show only logo, name, description, and IO prices
This commit is contained in:
parent
4fbdafee1c
commit
77b885b190
|
|
@ -378,7 +378,6 @@ const HomeModelList = () => {
|
||||||
<PricingCardView
|
<PricingCardView
|
||||||
filteredModels={pricingData.filteredModels}
|
filteredModels={pricingData.filteredModels}
|
||||||
loading={pricingData.loading}
|
loading={pricingData.loading}
|
||||||
rowSelection={null}
|
|
||||||
pageSize={pricingData.pageSize}
|
pageSize={pricingData.pageSize}
|
||||||
setPageSize={pricingData.setPageSize}
|
setPageSize={pricingData.setPageSize}
|
||||||
currentPage={pricingData.currentPage}
|
currentPage={pricingData.currentPage}
|
||||||
|
|
@ -387,9 +386,6 @@ const HomeModelList = () => {
|
||||||
groupRatio={pricingData.groupRatio}
|
groupRatio={pricingData.groupRatio}
|
||||||
groupModelPrice={pricingData.groupModelPrice}
|
groupModelPrice={pricingData.groupModelPrice}
|
||||||
groupModelRatio={pricingData.groupModelRatio}
|
groupModelRatio={pricingData.groupModelRatio}
|
||||||
copyText={pricingData.copyText}
|
|
||||||
setModalImageUrl={pricingData.setModalImageUrl}
|
|
||||||
setIsModalOpenurl={pricingData.setIsModalOpenurl}
|
|
||||||
currency={pricingData.currency}
|
currency={pricingData.currency}
|
||||||
siteDisplayType={pricingData.siteDisplayType}
|
siteDisplayType={pricingData.siteDisplayType}
|
||||||
tokenUnit={pricingData.tokenUnit}
|
tokenUnit={pricingData.tokenUnit}
|
||||||
|
|
@ -399,10 +395,7 @@ const HomeModelList = () => {
|
||||||
pricingData.channelVideoCompletionRatio
|
pricingData.channelVideoCompletionRatio
|
||||||
}
|
}
|
||||||
channelVideoPrice={pricingData.channelVideoPrice}
|
channelVideoPrice={pricingData.channelVideoPrice}
|
||||||
showRatio={false}
|
|
||||||
t={pricingData.t}
|
t={pricingData.t}
|
||||||
selectedRowKeys={[]}
|
|
||||||
setSelectedRowKeys={() => {}}
|
|
||||||
openModelDetail={pricingData.openModelDetail}
|
openModelDetail={pricingData.openModelDetail}
|
||||||
showSizeChanger={false}
|
showSizeChanger={false}
|
||||||
blurPricing={blurPricing}
|
blurPricing={blurPricing}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@ import { Card, Skeleton } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
const PricingCardSkeleton = ({
|
const PricingCardSkeleton = ({
|
||||||
skeletonCount = 100,
|
skeletonCount = 100,
|
||||||
rowSelection = false,
|
|
||||||
showRatio = false,
|
|
||||||
}) => {
|
}) => {
|
||||||
const placeholder = (
|
const placeholder = (
|
||||||
<div className='px-2 pt-2'>
|
<div className='px-2 pt-2'>
|
||||||
|
|
@ -34,8 +32,8 @@ const PricingCardSkeleton = ({
|
||||||
className='!rounded-2xl border border-gray-200'
|
className='!rounded-2xl border border-gray-200'
|
||||||
bodyStyle={{ padding: '24px' }}
|
bodyStyle={{ padding: '24px' }}
|
||||||
>
|
>
|
||||||
{/* 头部:图标 + 模型名称 + 操作按钮 */}
|
{/* 头部:图标 + 模型名称 */}
|
||||||
<div className='flex items-start justify-between mb-3'>
|
<div className='flex items-start mb-3'>
|
||||||
<div className='flex items-start space-x-3 flex-1 min-w-0'>
|
<div className='flex items-start space-x-3 flex-1 min-w-0'>
|
||||||
{/* 模型图标骨架 */}
|
{/* 模型图标骨架 */}
|
||||||
<div className='w-12 h-12 rounded-2xl flex items-center justify-center relative shadow-sm'>
|
<div className='w-12 h-12 rounded-2xl flex items-center justify-center relative shadow-sm'>
|
||||||
|
|
@ -44,41 +42,17 @@ const PricingCardSkeleton = ({
|
||||||
style={{ width: 48, height: 48, borderRadius: 16 }}
|
style={{ width: 48, height: 48, borderRadius: 16 }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* 模型名称和价格区域 */}
|
{/* 模型名称 */}
|
||||||
<div className='flex-1 min-w-0'>
|
<div className='flex-1 min-w-0'>
|
||||||
{/* 模型名称骨架 */}
|
|
||||||
<Skeleton.Title
|
<Skeleton.Title
|
||||||
style={{
|
style={{
|
||||||
width: `${120 + (index % 3) * 30}px`,
|
width: `${120 + (index % 3) * 30}px`,
|
||||||
height: 20,
|
height: 20,
|
||||||
marginBottom: 8,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* 价格信息骨架 */}
|
|
||||||
<Skeleton.Title
|
|
||||||
style={{
|
|
||||||
width: `${160 + (index % 4) * 20}px`,
|
|
||||||
height: 20,
|
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center space-x-2 ml-3'>
|
|
||||||
{/* 复制按钮骨架 */}
|
|
||||||
<Skeleton.Button
|
|
||||||
size='small'
|
|
||||||
style={{ width: 16, height: 16, borderRadius: 4 }}
|
|
||||||
/>
|
|
||||||
{/* 勾选框骨架 */}
|
|
||||||
{rowSelection && (
|
|
||||||
<Skeleton.Button
|
|
||||||
size='small'
|
|
||||||
style={{ width: 16, height: 16, borderRadius: 2 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 模型描述骨架 */}
|
{/* 模型描述骨架 */}
|
||||||
|
|
@ -90,43 +64,25 @@ const PricingCardSkeleton = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 标签区域骨架 */}
|
{/* 价格骨架:输入 + 输出并排 */}
|
||||||
<div className='flex flex-wrap gap-2'>
|
<div className='flex gap-3'>
|
||||||
{Array.from({ length: 2 + (index % 3) }).map((_, tagIndex) => (
|
<div className='flex-1 rounded-xl px-3 py-2.5' style={{backgroundColor: 'var(--semi-color-fill-0)'}}>
|
||||||
<Skeleton.Button
|
|
||||||
key={tagIndex}
|
|
||||||
size='small'
|
|
||||||
style={{
|
|
||||||
width: 64,
|
|
||||||
height: 18,
|
|
||||||
borderRadius: 10,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 倍率信息骨架(可选) */}
|
|
||||||
{showRatio && (
|
|
||||||
<div className='mt-4 pt-3 border-t border-gray-100'>
|
|
||||||
<div className='flex items-center space-x-1 mb-2'>
|
|
||||||
<Skeleton.Title
|
<Skeleton.Title
|
||||||
style={{ width: 60, height: 12, marginBottom: 0 }}
|
style={{ width: '80%', height: 14, marginBottom: 8 }}
|
||||||
/>
|
/>
|
||||||
<Skeleton.Button
|
|
||||||
size='small'
|
|
||||||
style={{ width: 14, height: 14, borderRadius: 7 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='grid grid-cols-3 gap-2'>
|
|
||||||
{Array.from({ length: 3 }).map((_, ratioIndex) => (
|
|
||||||
<Skeleton.Title
|
<Skeleton.Title
|
||||||
key={ratioIndex}
|
style={{ width: '100%', height: 18, marginBottom: 0 }}
|
||||||
style={{ width: '100%', height: 12, marginBottom: 0 }}
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex-1 rounded-xl px-3 py-2.5' style={{backgroundColor: 'var(--semi-color-fill-0)'}}>
|
||||||
|
<Skeleton.Title
|
||||||
|
style={{ width: '80%', height: 14, marginBottom: 8 }}
|
||||||
|
/>
|
||||||
|
<Skeleton.Title
|
||||||
|
style={{ width: '100%', height: 18, marginBottom: 0 }}
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,22 +20,15 @@ For commercial licensing, please contact support@quantumnous.com
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
Tag,
|
|
||||||
Tooltip,
|
|
||||||
Checkbox,
|
|
||||||
Empty,
|
Empty,
|
||||||
Pagination,
|
Pagination,
|
||||||
Button,
|
|
||||||
Avatar,
|
Avatar,
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import { IconHelpCircle } from '@douyinfe/semi-icons';
|
|
||||||
import { Copy } from 'lucide-react';
|
|
||||||
import {
|
import {
|
||||||
IllustrationNoResult,
|
IllustrationNoResult,
|
||||||
IllustrationNoResultDark,
|
IllustrationNoResultDark,
|
||||||
} from '@douyinfe/semi-illustrations';
|
} from '@douyinfe/semi-illustrations';
|
||||||
import {
|
import {
|
||||||
stringToColor,
|
|
||||||
calculateModelPrice,
|
calculateModelPrice,
|
||||||
getModelPriceItems,
|
getModelPriceItems,
|
||||||
getLobeHubIcon,
|
getLobeHubIcon,
|
||||||
|
|
@ -45,7 +38,6 @@ import {
|
||||||
} from '../../../../../helpers';
|
} from '../../../../../helpers';
|
||||||
import PricingCardSkeleton from './PricingCardSkeleton';
|
import PricingCardSkeleton from './PricingCardSkeleton';
|
||||||
import { useMinimumLoadingTime } from '../../../../../hooks/common/useMinimumLoadingTime';
|
import { useMinimumLoadingTime } from '../../../../../hooks/common/useMinimumLoadingTime';
|
||||||
import { renderLimitedItems } from '../../../../common/ui/RenderUtils';
|
|
||||||
import { useIsMobile } from '../../../../../hooks/common/useIsMobile';
|
import { useIsMobile } from '../../../../../hooks/common/useIsMobile';
|
||||||
const CARD_STYLES = {
|
const CARD_STYLES = {
|
||||||
container:
|
container:
|
||||||
|
|
@ -60,7 +52,6 @@ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
const PricingCardView = ({
|
const PricingCardView = ({
|
||||||
filteredModels,
|
filteredModels,
|
||||||
loading,
|
loading,
|
||||||
rowSelection,
|
|
||||||
pageSize,
|
pageSize,
|
||||||
setPageSize,
|
setPageSize,
|
||||||
currentPage,
|
currentPage,
|
||||||
|
|
@ -69,17 +60,11 @@ const PricingCardView = ({
|
||||||
groupRatio,
|
groupRatio,
|
||||||
groupModelPrice,
|
groupModelPrice,
|
||||||
groupModelRatio,
|
groupModelRatio,
|
||||||
copyText,
|
|
||||||
setModalImageUrl,
|
|
||||||
setIsModalOpenurl,
|
|
||||||
currency,
|
currency,
|
||||||
siteDisplayType,
|
siteDisplayType,
|
||||||
tokenUnit,
|
tokenUnit,
|
||||||
displayPrice,
|
displayPrice,
|
||||||
showRatio,
|
|
||||||
t,
|
t,
|
||||||
selectedRowKeys = [],
|
|
||||||
setSelectedRowKeys,
|
|
||||||
openModelDetail,
|
openModelDetail,
|
||||||
showSizeChanger = true,
|
showSizeChanger = true,
|
||||||
blurPricing = false,
|
blurPricing = false,
|
||||||
|
|
@ -122,62 +107,6 @@ const PricingCardView = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCheckboxChange = (model, checked) => {
|
|
||||||
if (!setSelectedRowKeys) return;
|
|
||||||
const modelKey = getModelKey(model);
|
|
||||||
const newKeys = checked
|
|
||||||
? Array.from(new Set([...selectedRowKeys, modelKey]))
|
|
||||||
: selectedRowKeys.filter((key) => key !== modelKey);
|
|
||||||
setSelectedRowKeys(newKeys);
|
|
||||||
rowSelection?.onChange?.(newKeys, null);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据 supplier_type 返回对应的 Tag 颜色
|
|
||||||
const getSupplierTypeColor = (supplierType) => {
|
|
||||||
switch (supplierType) {
|
|
||||||
case '公有云':
|
|
||||||
return 'green';
|
|
||||||
case 'AIDC':
|
|
||||||
return 'light-green';
|
|
||||||
case '企业中转站':
|
|
||||||
return 'lime';
|
|
||||||
case '个人中转站':
|
|
||||||
return 'yellow';
|
|
||||||
default:
|
|
||||||
return stringToColor(supplierType);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据模型的 channel_list 推导可展示的供应商项。
|
|
||||||
// 无 logo 时不展示 supplier_alias,仅保留供应商类型标签。
|
|
||||||
const getSupplierLogos = (model) => {
|
|
||||||
if (!model?.channel_list || model.channel_list.length === 0) return [];
|
|
||||||
const seen = new Set();
|
|
||||||
const items = [];
|
|
||||||
model.channel_list.forEach((ch, idx) => {
|
|
||||||
const logo =
|
|
||||||
(ch?.company_logo_url && String(ch.company_logo_url).trim()) || '';
|
|
||||||
const supplierType =
|
|
||||||
(ch?.supplier_type && String(ch.supplier_type).trim()) || '';
|
|
||||||
const alias =
|
|
||||||
(ch?.supplier_alias && String(ch.supplier_alias).trim()) || '';
|
|
||||||
const name = ch?.channel_name || '';
|
|
||||||
if (!logo && !supplierType) return;
|
|
||||||
const displayAlias = logo ? alias : '';
|
|
||||||
const dedupKey = `${logo}|${supplierType}|${displayAlias}`;
|
|
||||||
if (seen.has(dedupKey)) return;
|
|
||||||
seen.add(dedupKey);
|
|
||||||
items.push({
|
|
||||||
key: ch?.channel_id ?? `${dedupKey}-${idx}`,
|
|
||||||
logo,
|
|
||||||
supplierType,
|
|
||||||
alias: displayAlias,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return items;
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateChannelPrices = (model, opts = {}) => {
|
const calculateChannelPrices = (model, opts = {}) => {
|
||||||
const { skipSimpleVideoFlat = false, skipSimpleFixed = false } = opts;
|
const { skipSimpleVideoFlat = false, skipSimpleFixed = false } = opts;
|
||||||
if (!model.channel_list || model.channel_list.length === 0) {
|
if (!model.channel_list || model.channel_list.length === 0) {
|
||||||
|
|
@ -766,77 +695,11 @@ const PricingCardView = ({
|
||||||
return record.description || '';
|
return record.description || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 渲染标签
|
|
||||||
const renderTags = (record) => {
|
|
||||||
// 计费类型标签(左边)- 使用 channel_list[0].quota_type
|
|
||||||
const channelQuotaType =
|
|
||||||
record.channel_list && record.channel_list.length > 0
|
|
||||||
? record.channel_list[0].quota_type
|
|
||||||
: record.quota_type;
|
|
||||||
|
|
||||||
let billingTag = (
|
|
||||||
<Tag key='billing' shape='circle' color='white' size='small'>
|
|
||||||
-
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
if (channelQuotaType === 1) {
|
|
||||||
billingTag = (
|
|
||||||
<Tag key='billing' shape='circle' color='teal' size='small'>
|
|
||||||
{t('按次计费')}
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
} else if (channelQuotaType === 0) {
|
|
||||||
billingTag = (
|
|
||||||
<Tag key='billing' shape='circle' color='violet' size='small'>
|
|
||||||
{t('按量计费')}
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义标签(右边)
|
|
||||||
const customTags = [];
|
|
||||||
if (record.tags) {
|
|
||||||
const tagArr = record.tags.split(',').filter(Boolean);
|
|
||||||
tagArr.forEach((tg, idx) => {
|
|
||||||
customTags.push(
|
|
||||||
<Tag
|
|
||||||
key={`custom-${idx}`}
|
|
||||||
shape='circle'
|
|
||||||
color={stringToColor(tg)}
|
|
||||||
size='small'
|
|
||||||
>
|
|
||||||
{renderHighlightedText(tg)}
|
|
||||||
</Tag>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className='flex items-center gap-2'>{billingTag}</div>
|
|
||||||
<div className='flex items-center gap-1'>
|
|
||||||
{customTags.length > 0 &&
|
|
||||||
renderLimitedItems({
|
|
||||||
items: customTags.map((tag, idx) => ({
|
|
||||||
key: `custom-${idx}`,
|
|
||||||
element: tag,
|
|
||||||
})),
|
|
||||||
renderItem: (item, idx) => item.element,
|
|
||||||
maxDisplay: 3,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 显示骨架屏
|
// 显示骨架屏
|
||||||
if (showSkeleton) {
|
if (showSkeleton) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PricingCardSkeleton
|
<PricingCardSkeleton />
|
||||||
rowSelection={!!rowSelection}
|
|
||||||
showRatio={showRatio}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -863,7 +726,6 @@ const PricingCardView = ({
|
||||||
<div className={gridCols > 0 ? 'grid gap-4' : 'flex flex-wrap gap-4'} style={gridCols > 0 ? { gridTemplateColumns: `repeat(${gridCols}, 1fr)` } : undefined}>
|
<div className={gridCols > 0 ? 'grid gap-4' : 'flex flex-wrap gap-4'} style={gridCols > 0 ? { gridTemplateColumns: `repeat(${gridCols}, 1fr)` } : undefined}>
|
||||||
{paginatedModels.map((model, index) => {
|
{paginatedModels.map((model, index) => {
|
||||||
const modelKey = getModelKey(model);
|
const modelKey = getModelKey(model);
|
||||||
const isSelected = selectedRowKeys.includes(modelKey);
|
|
||||||
|
|
||||||
const priceData = calculateModelPrice({
|
const priceData = calculateModelPrice({
|
||||||
record: model,
|
record: model,
|
||||||
|
|
@ -877,166 +739,31 @@ const PricingCardView = ({
|
||||||
quotaDisplayType: siteDisplayType,
|
quotaDisplayType: siteDisplayType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const supplierLogos = getSupplierLogos(model);
|
|
||||||
const hasChannelList =
|
|
||||||
Array.isArray(model.channel_list) &&
|
|
||||||
model.channel_list.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={modelKey || index}
|
key={modelKey || index}
|
||||||
className={`${gridCols > 0 ? '!w-full' : 'flex-1 min-w-[350px] max-w-[600px]'} !rounded-2xl transition-all duration-200 hover:shadow-lg border ${blurPricing ? '' : 'cursor-pointer'} ${isSelected ? CARD_STYLES.selected : CARD_STYLES.default}`}
|
className={`${gridCols > 0 ? '!w-full' : 'flex-1 min-w-[350px] max-w-[600px]'} !rounded-2xl transition-all duration-200 hover:shadow-lg border ${blurPricing ? '' : 'cursor-pointer'} ${CARD_STYLES.default}`}
|
||||||
bodyStyle={{ height: '100%' }}
|
bodyStyle={{ height: '100%' }}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
!blurPricing && openModelDetail && openModelDetail(model)
|
!blurPricing && openModelDetail && openModelDetail(model)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className='flex flex-col h-full'>
|
<div className='flex flex-col h-full'>
|
||||||
{/* 头部:图标 + 模型名称 + 操作按钮 */}
|
{/* 头部:图标 + 模型名称 */}
|
||||||
<div className='flex items-start justify-between mb-3'>
|
<div className='flex items-start mb-3'>
|
||||||
<div className='flex items-start space-x-3 flex-1 min-w-0'>
|
<div className='flex items-start space-x-3 flex-1 min-w-0'>
|
||||||
{getModelIcon(model)}
|
{getModelIcon(model)}
|
||||||
<div className='flex-1 min-w-0'>
|
<div className='flex-1 min-w-0'>
|
||||||
<h3 className='text-lg font-bold text-gray-900 truncate'>
|
<h3 className='text-lg font-bold text-gray-900 truncate'>
|
||||||
{renderHighlightedText(model.model_name)}
|
{renderHighlightedText(model.model_name)}
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
|
||||||
className='flex flex-col gap-1 text-xs mt-1'
|
|
||||||
style={
|
|
||||||
blurPricing
|
|
||||||
? {
|
|
||||||
filter: 'blur(6px)',
|
|
||||||
userSelect: 'none',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{getModelPriceItemsForCard(model, priceData).map(
|
|
||||||
(item) => (
|
|
||||||
<div key={item.key} className='flex items-center'>
|
|
||||||
<span className='w-20 flex-shrink-0'>
|
|
||||||
{item.label}
|
|
||||||
</span>
|
|
||||||
<span className='flex-1 font-bold text-black inline-flex items-center flex-wrap gap-1'>
|
|
||||||
{item.valueNode ? (
|
|
||||||
item.valueNode
|
|
||||||
) : item.original ? (
|
|
||||||
<>
|
|
||||||
<span className='line-through text-gray-400 font-normal text-[10px]'>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: 'var(--semi-color-primary)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
官方
|
|
||||||
</span>{' '}
|
|
||||||
{item.original.text}
|
|
||||||
</span>
|
|
||||||
<Tag
|
|
||||||
color='red'
|
|
||||||
size='small'
|
|
||||||
shape='circle'
|
|
||||||
>
|
|
||||||
-{item.original.discount}%
|
|
||||||
</Tag>
|
|
||||||
<span>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: 'var(--semi-color-warning)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
我们
|
|
||||||
</span>{' '}
|
|
||||||
{item.value}
|
|
||||||
{item.suffix}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
{item.value}
|
|
||||||
{item.suffix}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
<div className='flex items-center'>
|
|
||||||
<span className='w-20 flex-shrink-0'>
|
|
||||||
{t('供应商')}
|
|
||||||
</span>
|
|
||||||
<div className='flex-1 flex items-center flex-wrap gap-1'>
|
|
||||||
{supplierLogos.length === 0 ? (
|
|
||||||
hasChannelList ? null : (
|
|
||||||
<span className='font-bold text-black'>
|
|
||||||
{t('官方')}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
supplierLogos.map((s) => (
|
|
||||||
<div
|
|
||||||
key={s.key}
|
|
||||||
className='h-7 rounded-md flex items-center overflow-hidden'
|
|
||||||
style={{backgroundColor: 'var(--semi-color-fill-0)'}}
|
|
||||||
>
|
|
||||||
{s.logo ? (
|
|
||||||
<img
|
|
||||||
src={s.logo}
|
|
||||||
alt={s.alias || s.name || ''}
|
|
||||||
className='w-7 h-7 object-contain rounded-md'
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{s.supplierType && (
|
|
||||||
<Tag
|
|
||||||
size='small'
|
|
||||||
shape='circle'
|
|
||||||
color={getSupplierTypeColor(
|
|
||||||
s.supplierType,
|
|
||||||
)}
|
|
||||||
className='mx-1'
|
|
||||||
>
|
|
||||||
{s.supplierType}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center space-x-2 ml-3'>
|
{/* 模型描述 */}
|
||||||
{/* 复制按钮 */}
|
|
||||||
<Button
|
|
||||||
size='small'
|
|
||||||
theme='outline'
|
|
||||||
type='tertiary'
|
|
||||||
icon={<Copy size={12} />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
copyText(model.model_name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 选择框 */}
|
|
||||||
{rowSelection && (
|
|
||||||
<Checkbox
|
|
||||||
checked={isSelected}
|
|
||||||
onChange={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleCheckboxChange(model, e.target.checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 模型描述 - 占据剩余空间 */}
|
|
||||||
<div
|
<div
|
||||||
className='flex-1 mb-4'
|
className='mb-4'
|
||||||
style={
|
style={
|
||||||
blurPricing
|
blurPricing
|
||||||
? {
|
? {
|
||||||
|
|
@ -1055,7 +782,7 @@ const PricingCardView = ({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 底部区域 */}
|
{/* 价格:输入 + 输出并排 */}
|
||||||
<div
|
<div
|
||||||
className='mt-auto'
|
className='mt-auto'
|
||||||
style={
|
style={
|
||||||
|
|
@ -1068,49 +795,39 @@ const PricingCardView = ({
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* 标签区域 */}
|
{(() => {
|
||||||
{renderTags(model)}
|
const items = getModelPriceItemsForCard(model, priceData);
|
||||||
|
const inputItem = items.find(i => i.key === 'input');
|
||||||
{/* 倍率信息(可选) */}
|
const outputItem = items.find(i => i.key === 'output');
|
||||||
{showRatio && (
|
return (
|
||||||
<div className='pt-3'>
|
<div className='flex gap-3'>
|
||||||
<div className='flex items-center space-x-1 mb-2'>
|
{inputItem && (
|
||||||
<span className='text-xs font-medium text-gray-700'>
|
<div className='flex-1 rounded-xl px-3 py-2.5' style={{backgroundColor: 'var(--semi-color-fill-0)'}}>
|
||||||
{t('倍率信息')}
|
<div className='text-[10px] mb-0.5' style={{color: 'var(--semi-color-text-2)'}}>{inputItem.label}</div>
|
||||||
</span>
|
<div className='text-sm font-bold text-gray-900'>
|
||||||
<Tooltip
|
{inputItem.value}{inputItem.suffix}
|
||||||
content={t('倍率是为了方便换算不同价格的模型')}
|
|
||||||
>
|
|
||||||
<IconHelpCircle
|
|
||||||
className='text-blue-500 cursor-pointer'
|
|
||||||
size='small'
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setModalImageUrl('/ratio.png');
|
|
||||||
setIsModalOpenurl(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div className='grid grid-cols-3 gap-2 text-xs text-gray-600'>
|
|
||||||
<div>
|
|
||||||
{t('模型')}:{' '}
|
|
||||||
{model.quota_type === 0
|
|
||||||
? (priceData?.inputRatio ?? model.model_ratio)
|
|
||||||
: t('无')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{t('输出')}:{' '}
|
|
||||||
{model.quota_type === 0
|
|
||||||
? parseFloat(model.completion_ratio.toFixed(2))
|
|
||||||
: t('无')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{t('分组')}: {priceData?.usedGroupRatio ?? '-'}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{outputItem && (
|
||||||
|
<div className='flex-1 rounded-xl px-3 py-2.5' style={{backgroundColor: 'var(--semi-color-fill-0)'}}>
|
||||||
|
<div className='text-[10px] mb-0.5' style={{color: 'var(--semi-color-text-2)'}}>{outputItem.label}</div>
|
||||||
|
<div className='text-sm font-bold text-gray-900'>
|
||||||
|
{outputItem.value}{outputItem.suffix}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!inputItem && !outputItem && items.map(item => (
|
||||||
|
<div key={item.key} className='flex-1 rounded-xl px-3 py-2.5' style={{backgroundColor: 'var(--semi-color-fill-0)'}}>
|
||||||
|
<div className='text-[10px] mb-0.5' style={{color: 'var(--semi-color-text-2)'}}>{item.label}</div>
|
||||||
|
<div className='text-sm font-bold text-gray-900'>
|
||||||
|
{item.valueNode || (item.value + (item.suffix || ''))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue