TuringToken/web/src/components/table/model-pricing/layout/ModelsSidebar.jsx

148 lines
4.7 KiB
JavaScript

/*
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.
For commercial licensing, please contact support@quantumnous.com
*/
import React from 'react';
import { usePricingFilterCounts } from '../../../../hooks/model-pricing/usePricingFilterCounts';
import { getLobeHubIcon } from '../../../../helpers';
const ModelsSidebar = ({
models,
filterVendor, setFilterVendor,
filterSupplierType, setFilterSupplierType,
filterTag, setFilterTag,
filterGroup, filterQuotaType, filterEndpointType, searchValue,
loading, t,
}) => {
const { vendorModels, tagModels, supplierTypeModels } = usePricingFilterCounts({
models,
filterGroup,
filterQuotaType,
filterEndpointType,
filterVendor,
filterTag,
filterSupplierType,
searchValue,
});
// ─── 作者 (Vendors) ───
const vendors = React.useMemo(() => {
const names = [...new Set(models.map((m) => m.vendor_name).filter(Boolean))].sort();
return names.map((name) => {
const icon = models.find((m) => m.vendor_name === name)?.vendor_icon;
return {
name,
icon,
count: vendorModels.filter((m) => m.vendor_name === name).length,
};
});
}, [models, vendorModels]);
// ─── 服务商 (Supplier Types) ───
const supplierTypes = React.useMemo(() => {
const types = new Set();
models.forEach((m) => {
(m.channel_list || []).forEach((ch) => {
if (ch.supplier_type) types.add(ch.supplier_type);
});
});
return [...types].sort();
}, [models]);
// ─── 输入模态 (Tags) ───
const tags = React.useMemo(() => {
const allTags = new Set();
models.forEach((m) => {
if (m.tags) {
String(m.tags).split(/[,;|]/).map(s => s.trim()).filter(Boolean).forEach(t => allTags.add(t.toLowerCase()));
}
});
return [...allTags].sort();
}, [models]);
const FilterItem = ({ label, count, active, onClick }) => (
<div
className={`models-filter-item${active ? ' active' : ''}`}
onClick={onClick}
>
<span>{label}</span>
<span className='count'>{count}</span>
</div>
);
return (
<div>
{/* 作者 */}
<div className='models-filter-section'>
<div className='models-filter-title'>{t('作者')}</div>
<FilterItem label={t('全部')} count={models.length} active={filterVendor === 'all'} onClick={() => setFilterVendor('all')} />
{vendors.map((v) => (
<FilterItem
key={v.name}
label={
<span className='flex items-center gap-2'>
{v.icon && (
<img
src={getLobeHubIcon(v.icon)}
alt=''
className='w-4 h-4 rounded-full'
onError={(e) => { e.target.style.display = 'none'; }}
/>
)}
<span>{v.name}</span>
</span>
}
count={v.count}
active={filterVendor === v.name}
onClick={() => setFilterVendor(filterVendor === v.name ? 'all' : v.name)}
/>
))}
</div>
{/* 服务商 */}
<div className='models-filter-section'>
<div className='models-filter-title'>{t('服务商')}</div>
<FilterItem label={t('全部')} count={models.length} active={filterSupplierType === 'all'} onClick={() => setFilterSupplierType('all')} />
{supplierTypes.map((type) => (
<FilterItem
key={type}
label={type}
count={supplierTypeModels.filter((m) =>
(m.channel_list || []).some((ch) => ch.supplier_type === type)
).length}
active={filterSupplierType === type}
onClick={() => setFilterSupplierType(filterSupplierType === type ? 'all' : type)}
/>
))}
</div>
{/* 输入模态 */}
<div className='models-filter-section'>
<div className='models-filter-title'>{t('输入模态')}</div>
<FilterItem label={t('全部')} count={models.length} active={filterTag === 'all'} onClick={() => setFilterTag('all')} />
{tags.map((tag) => (
<FilterItem
key={tag}
label={tag}
count={tagModels.filter((m) =>
m.tags && String(m.tags).split(/[,;|]/).map(s => s.trim()).includes(tag)
).length}
active={filterTag === tag}
onClick={() => setFilterTag(filterTag === tag ? 'all' : tag)}
/>
))}
</div>
</div>
);
};
export default ModelsSidebar;