/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React from 'react'; import { Card, Tag, Tooltip, Checkbox, Empty, Pagination, Button, Avatar } from '@douyinfe/semi-ui'; import { IconHelpCircle, IconCopy } from '@douyinfe/semi-icons'; import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations'; import { stringToColor, calculateModelPrice, formatPriceInfo, getLobeHubIcon } from '../../../../../helpers'; import PricingCardSkeleton from './PricingCardSkeleton'; import { useMinimumLoadingTime } from '../../../../../hooks/common/useMinimumLoadingTime'; import { renderLimitedItems } from '../../../../common/ui/RenderUtils'; import { useIsMobile } from '../../../../../hooks/common/useIsMobile'; 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", selected: "border-blue-500 bg-blue-50", default: "border-gray-200 hover:border-gray-300" }; const PricingCardView = ({ filteredModels, loading, rowSelection, pageSize, setPageSize, currentPage, setCurrentPage, selectedGroup, groupRatio, copyText, setModalImageUrl, setIsModalOpenurl, currency, tokenUnit, displayPrice, showRatio, t, selectedRowKeys = [], setSelectedRowKeys, openModelDetail, }) => { const showSkeleton = useMinimumLoadingTime(loading); const startIndex = (currentPage - 1) * pageSize; const paginatedModels = filteredModels.slice(startIndex, startIndex + pageSize); const getModelKey = (model) => model.key ?? model.model_name ?? model.id; const isMobile = useIsMobile(); 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); }; // 获取模型图标 const getModelIcon = (model) => { if (!model || !model.model_name) { return (
?
); } // 1) 优先使用模型自定义图标 if (model.icon) { return (
{getLobeHubIcon(model.icon, 32)}
); } // 2) 退化为供应商图标 if (model.vendor_icon) { return (
{getLobeHubIcon(model.vendor_icon, 32)}
); } // 如果没有供应商图标,使用模型名称生成头像 const avatarText = model.model_name.slice(0, 2).toUpperCase(); return (
{avatarText}
); }; // 获取模型描述 const getModelDescription = (record) => { return record.description || ''; }; // 渲染价格信息 const renderPriceInfo = (record) => { const priceData = calculateModelPrice({ record, selectedGroup, groupRatio, tokenUnit, displayPrice, currency, }); return formatPriceInfo(priceData, t); }; // 渲染标签 const renderTags = (record) => { // 计费类型标签(左边) let billingTag = ( - ); if (record.quota_type === 1) { billingTag = ( {t('按次计费')} ); } else if (record.quota_type === 0) { billingTag = ( {t('按量计费')} ); } // 自定义标签(右边) const customTags = []; if (record.tags) { const tagArr = record.tags.split(',').filter(Boolean); tagArr.forEach((tg, idx) => { customTags.push( {tg} ); }); } return (
{billingTag}
{customTags.length > 0 && renderLimitedItems({ items: customTags.map((tag, idx) => ({ key: `custom-${idx}`, element: tag })), renderItem: (item, idx) => item.element, maxDisplay: 3 })}
); }; // 显示骨架屏 if (showSkeleton) { return ( ); } if (!filteredModels || filteredModels.length === 0) { return (
} darkModeImage={} description={t('搜索无结果')} />
); } return (
{paginatedModels.map((model, index) => { const modelKey = getModelKey(model); const isSelected = selectedRowKeys.includes(modelKey); return ( openModelDetail && openModelDetail(model)} >
{/* 头部:图标 + 模型名称 + 操作按钮 */}
{getModelIcon(model)}

{model.model_name}

{renderPriceInfo(model)}
{/* 复制按钮 */}
{/* 模型描述 - 占据剩余空间 */}

{getModelDescription(model)}

{/* 底部区域 */}
{/* 标签区域 */}
{renderTags(model)}
{/* 倍率信息(可选) */} {showRatio && (
{t('倍率信息')} { e.stopPropagation(); setModalImageUrl('/ratio.png'); setIsModalOpenurl(true); }} />
{t('模型')}: {model.quota_type === 0 ? model.model_ratio : t('无')}
{t('补全')}: {model.quota_type === 0 ? parseFloat(model.completion_ratio.toFixed(3)) : t('无')}
{t('分组')}: {priceData.usedGroupRatio}
)}
); })}
{/* 分页 */} {filteredModels.length > 0 && (
setCurrentPage(page)} onPageSizeChange={(size) => { setPageSize(size); setCurrentPage(1); }} />
)}
); }; export default PricingCardView;