148 lines
4.7 KiB
JavaScript
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;
|