🎨 refactor(ModelPricing): enhance UI/UX with modern design ModelPricing component

This commit implements a comprehensive UI/UX overhaul of the ModelPricing component,
focusing on improved aesthetics and responsiveness while maintaining existing API logic.

Key improvements:
- Redesigned status card with gradient background and floating elements
- Implemented responsive grid layout for pricing metrics
- Enhanced visual hierarchy with Semi UI components
- Added smooth transitions and hover effects
- Optimized spacing and typography for better readability
- Unified design language with PersonalSettings component
- Integrated Tailwind CSS 3.0 utility classes
- Added decorative elements for visual interest
- Improved mobile responsiveness across all breakpoints
- Enhanced accessibility with proper contrast ratios

The redesign follows modern UI/UX best practices while maintaining consistency
with the application's design system.
This commit is contained in:
Apple\Apple
2025-05-25 17:27:45 +08:00
parent 33ae3479c4
commit 00c1ff05de
5 changed files with 499 additions and 162 deletions

View File

@@ -3,7 +3,6 @@ import { API, copy, showError, showInfo, showSuccess } from '../helpers';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Banner,
Input, Input,
Layout, Layout,
Modal, Modal,
@@ -14,15 +13,21 @@ import {
Popover, Popover,
ImagePreview, ImagePreview,
Button, Button,
Card,
Tabs,
TabPane,
Dropdown,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { import {
IconMore,
IconVerify, IconVerify,
IconUploadError,
IconHelpCircle, IconHelpCircle,
IconSearch,
IconCopy,
IconInfoCircle,
} from '@douyinfe/semi-icons'; } from '@douyinfe/semi-icons';
import { UserContext } from '../context/User/index.js'; import { UserContext } from '../context/User/index.js';
import Text from '@douyinfe/semi-ui/lib/es/typography/text'; import { Settings, AlertCircle } from 'lucide-react';
import { MODEL_CATEGORIES } from '../constants';
const ModelPricing = () => { const ModelPricing = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -32,6 +37,8 @@ const ModelPricing = () => {
const [modalImageUrl, setModalImageUrl] = useState(''); const [modalImageUrl, setModalImageUrl] = useState('');
const [isModalOpenurl, setIsModalOpenurl] = useState(false); const [isModalOpenurl, setIsModalOpenurl] = useState(false);
const [selectedGroup, setSelectedGroup] = useState('default'); const [selectedGroup, setSelectedGroup] = useState('default');
const [activeKey, setActiveKey] = useState('all');
const [pageSize, setPageSize] = useState(10);
const rowSelection = useMemo( const rowSelection = useMemo(
() => ({ () => ({
@@ -49,6 +56,7 @@ const ModelPricing = () => {
const newFilteredValue = value ? [value] : []; const newFilteredValue = value ? [value] : [];
setFilteredValue(newFilteredValue); setFilteredValue(newFilteredValue);
}; };
const handleCompositionStart = () => { const handleCompositionStart = () => {
compositionRef.current.isComposition = true; compositionRef.current.isComposition = true;
}; };
@@ -61,17 +69,16 @@ const ModelPricing = () => {
}; };
function renderQuotaType(type) { function renderQuotaType(type) {
// Ensure all cases are string literals by adding quotes.
switch (type) { switch (type) {
case 1: case 1:
return ( return (
<Tag color='teal' size='large'> <Tag color='teal' size='large' shape='circle'>
{t('按次计费')} {t('按次计费')}
</Tag> </Tag>
); );
case 0: case 0:
return ( return (
<Tag color='violet' size='large'> <Tag color='violet' size='large' shape='circle'>
{t('按量计费')} {t('按量计费')}
</Tag> </Tag>
); );
@@ -88,15 +95,9 @@ const ModelPricing = () => {
} }
position='top' position='top'
key={available} key={available}
style={{ className="bg-green-50"
backgroundColor: 'rgba(var(--semi-blue-4),1)',
borderColor: 'rgba(var(--semi-blue-4),1)',
color: 'var(--semi-color-white)',
borderWidth: 1,
borderStyle: 'solid',
}}
> >
<IconVerify style={{ color: 'green' }} size='large' /> <IconVerify style={{ color: 'rgb(22 163 74)' }} size='large' />
</Popover> </Popover>
) : null; ) : null;
} }
@@ -106,7 +107,6 @@ const ModelPricing = () => {
title: t('可用性'), title: t('可用性'),
dataIndex: 'available', dataIndex: 'available',
render: (text, record, index) => { render: (text, record, index) => {
// if record.enable_groups contains selectedGroup, then available is true
return renderAvailable(record.enable_groups.includes(selectedGroup)); return renderAvailable(record.enable_groups.includes(selectedGroup));
}, },
sorter: (a, b) => { sorter: (a, b) => {
@@ -115,28 +115,29 @@ const ModelPricing = () => {
return Number(aAvailable) - Number(bAvailable); return Number(aAvailable) - Number(bAvailable);
}, },
defaultSortOrder: 'descend', defaultSortOrder: 'descend',
width: 100,
}, },
{ {
title: t('模型名称'), title: t('模型名称'),
dataIndex: 'model_name', dataIndex: 'model_name',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<> <Tag
<Tag color='green'
color='green' size='large'
size='large' shape='circle'
onClick={() => { onClick={() => {
copyText(text); copyText(text);
}} }}
> >
{text} {text}
</Tag> </Tag>
</>
); );
}, },
onFilter: (value, record) => onFilter: (value, record) =>
record.model_name.toLowerCase().includes(value.toLowerCase()), record.model_name.toLowerCase().includes(value.toLowerCase()),
filteredValue, filteredValue,
width: 200,
}, },
{ {
title: t('计费类型'), title: t('计费类型'),
@@ -145,19 +146,19 @@ const ModelPricing = () => {
return renderQuotaType(parseInt(text)); return renderQuotaType(parseInt(text));
}, },
sorter: (a, b) => a.quota_type - b.quota_type, sorter: (a, b) => a.quota_type - b.quota_type,
width: 120,
}, },
{ {
title: t('可用分组'), title: t('可用分组'),
dataIndex: 'enable_groups', dataIndex: 'enable_groups',
render: (text, record, index) => { render: (text, record, index) => {
// enable_groups is a string array
return ( return (
<Space> <Space wrap>
{text.map((group) => { {text.map((group) => {
if (usableGroup[group]) { if (usableGroup[group]) {
if (group === selectedGroup) { if (group === selectedGroup) {
return ( return (
<Tag color='blue' size='large' prefixIcon={<IconVerify />}> <Tag color='blue' size='large' shape='circle' prefixIcon={<IconVerify />}>
{group} {group}
</Tag> </Tag>
); );
@@ -175,6 +176,7 @@ const ModelPricing = () => {
}), }),
); );
}} }}
className="cursor-pointer hover:opacity-80 transition-opacity !rounded-full"
> >
{group} {group}
</Tag> </Tag>
@@ -188,56 +190,40 @@ const ModelPricing = () => {
}, },
{ {
title: () => ( title: () => (
<span style={{ display: 'flex', alignItems: 'center' }}> <div className="flex items-center space-x-1">
{t('倍率')} <span>{t('倍率')}</span>
<Popover <Tooltip content={t('倍率是为了方便换算不同价格的模型')}>
content={
<div style={{ padding: 8 }}>
{t('倍率是为了方便换算不同价格的模型')}
<br />
{t('点击查看倍率说明')}
</div>
}
position='top'
style={{
backgroundColor: 'rgba(var(--semi-blue-4),1)',
borderColor: 'rgba(var(--semi-blue-4),1)',
color: 'var(--semi-color-white)',
borderWidth: 1,
borderStyle: 'solid',
}}
>
<IconHelpCircle <IconHelpCircle
className="text-blue-500 cursor-pointer"
onClick={() => { onClick={() => {
setModalImageUrl('/ratio.png'); setModalImageUrl('/ratio.png');
setIsModalOpenurl(true); setIsModalOpenurl(true);
}} }}
/> />
</Popover> </Tooltip>
</span> </div>
), ),
dataIndex: 'model_ratio', dataIndex: 'model_ratio',
render: (text, record, index) => { render: (text, record, index) => {
let content = text; let content = text;
let completionRatio = parseFloat(record.completion_ratio.toFixed(3)); let completionRatio = parseFloat(record.completion_ratio.toFixed(3));
content = ( content = (
<> <div className="space-y-1">
<Text> <div className="text-gray-700">
{t('模型倍率')}{record.quota_type === 0 ? text : t('无')} {t('模型倍率')}{record.quota_type === 0 ? text : t('无')}
</Text> </div>
<br /> <div className="text-gray-700">
<Text>
{t('补全倍率')} {t('补全倍率')}
{record.quota_type === 0 ? completionRatio : t('无')} {record.quota_type === 0 ? completionRatio : t('无')}
</Text> </div>
<br /> <div className="text-gray-700">
<Text>
{t('分组倍率')}{groupRatio[selectedGroup]} {t('分组倍率')}{groupRatio[selectedGroup]}
</Text> </div>
</> </div>
); );
return <div>{content}</div>; return content;
}, },
width: 200,
}, },
{ {
title: t('模型价格'), title: t('模型价格'),
@@ -245,7 +231,6 @@ const ModelPricing = () => {
render: (text, record, index) => { render: (text, record, index) => {
let content = text; let content = text;
if (record.quota_type === 0) { if (record.quota_type === 0) {
// 这里的 *2 是因为 1倍率=0.002刀,请勿删除
let inputRatioPrice = let inputRatioPrice =
record.model_ratio * 2 * groupRatio[selectedGroup]; record.model_ratio * 2 * groupRatio[selectedGroup];
let completionRatioPrice = let completionRatioPrice =
@@ -254,26 +239,26 @@ const ModelPricing = () => {
2 * 2 *
groupRatio[selectedGroup]; groupRatio[selectedGroup];
content = ( content = (
<> <div className="space-y-1">
<Text> <div className="text-gray-700">
{t('提示')} ${inputRatioPrice} / 1M tokens {t('提示')} ${inputRatioPrice.toFixed(3)} / 1M tokens
</Text> </div>
<br /> <div className="text-gray-700">
<Text> {t('补全')} ${completionRatioPrice.toFixed(3)} / 1M tokens
{t('补全')} ${completionRatioPrice} / 1M tokens </div>
</Text> </div>
</>
); );
} else { } else {
let price = parseFloat(text) * groupRatio[selectedGroup]; let price = parseFloat(text) * groupRatio[selectedGroup];
content = ( content = (
<> <div className="text-gray-700">
${t('模型价格')}${price} ${t('模型价格')}${price.toFixed(3)}
</> </div>
); );
} }
return <div>{content}</div>; return content;
}, },
width: 250,
}, },
]; ];
@@ -288,12 +273,10 @@ const ModelPricing = () => {
models[i].key = models[i].model_name; models[i].key = models[i].model_name;
models[i].group_ratio = groupRatio[models[i].model_name]; models[i].group_ratio = groupRatio[models[i].model_name];
} }
// sort by quota_type
models.sort((a, b) => { models.sort((a, b) => {
return a.quota_type - b.quota_type; return a.quota_type - b.quota_type;
}); });
// sort by model_name, start with gpt is max, other use localeCompare
models.sort((a, b) => { models.sort((a, b) => {
if (a.model_name.startsWith('gpt') && !b.model_name.startsWith('gpt')) { if (a.model_name.startsWith('gpt') && !b.model_name.startsWith('gpt')) {
return -1; return -1;
@@ -312,9 +295,7 @@ const ModelPricing = () => {
const loadPricing = async () => { const loadPricing = async () => {
setLoading(true); setLoading(true);
let url = '/api/pricing';
let url = '';
url = `/api/pricing`;
const res = await API.get(url); const res = await API.get(url);
const { success, message, data, group_ratio, usable_group } = res.data; const { success, message, data, group_ratio, usable_group } = res.data;
if (success) { if (success) {
@@ -334,10 +315,9 @@ const ModelPricing = () => {
const copyText = async (text) => { const copyText = async (text) => {
if (await copy(text)) { if (await copy(text)) {
showSuccess('已复制:' + text); showSuccess(t('已复制:') + text);
} else { } else {
// setSearchKeyword(text); Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text });
Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
} }
}; };
@@ -345,88 +325,284 @@ const ModelPricing = () => {
refresh().then(); refresh().then();
}, []); }, []);
return ( const modelCategories = MODEL_CATEGORIES(t);
<>
<Layout> const renderArrow = (items, pos, handleArrowClick) => {
{userState.user ? ( const style = {
<Banner width: 32,
type='success' height: 32,
fullMode={false} margin: '0 12px',
closeIcon='null' display: 'flex',
description={t('您的默认分组为:{{group}},分组倍率为:{{ratio}}', { justifyContent: 'center',
group: userState.user.group, alignItems: 'center',
ratio: groupRatio[userState.user.group], borderRadius: '100%',
})} background: 'rgba(var(--semi-grey-1), 1)',
/> color: 'var(--semi-color-text)',
) : ( cursor: 'pointer',
<Banner };
type='warning' return (
fullMode={false} <Dropdown
closeIcon='null' render={
description={t('您还未登陆,显示的价格为默认分组倍率: {{ratio}}', { <Dropdown.Menu>
ratio: groupRatio['default'], {items.map(item => (
})} <Dropdown.Item
/> key={item.itemKey}
)} onClick={() => setActiveKey(item.itemKey)}
<br /> icon={modelCategories[item.itemKey]?.icon}
<Banner >
type='info' {modelCategories[item.itemKey]?.label || item.itemKey}
fullMode={false} </Dropdown.Item>
description={ ))}
<div> </Dropdown.Menu>
{t( }
'按量计费费用 = 分组倍率 × 模型倍率 × 提示token数 + 补全token数 × 补全倍率)/ 500000 (单位:美元)', >
)} <div style={style} onClick={handleArrowClick}>
</div> {pos === 'start' ? '←' : '→'}
} </div>
closeIcon='null' </Dropdown>
/> );
<br /> };
<Space style={{ marginBottom: 16 }}>
// 检查分类是否有对应的模型
const availableCategories = useMemo(() => {
if (!models.length) return ['all'];
return Object.entries(modelCategories).filter(([key, category]) => {
if (key === 'all') return true;
return models.some(model => category.filter(model));
}).map(([key]) => key);
}, [models]);
// 渲染标签页
const renderTabs = () => {
return (
<Tabs
renderArrow={renderArrow}
activeKey={activeKey}
type="card"
collapsible
onChange={key => setActiveKey(key)}
>
{Object.entries(modelCategories)
.filter(([key]) => availableCategories.includes(key))
.map(([key, category]) => (
<TabPane
tab={
<span className="flex items-center gap-2">
{category.icon && <span className="w-4 h-4">{category.icon}</span>}
{category.label}
</span>
}
itemKey={key}
key={key}
/>
))}
</Tabs>
);
};
// 优化过滤逻辑
const filteredModels = useMemo(() => {
let result = models;
// 先按分类过滤
if (activeKey !== 'all') {
result = result.filter(model => modelCategories[activeKey].filter(model));
}
// 再按搜索词过滤
if (filteredValue.length > 0) {
const searchTerm = filteredValue[0].toLowerCase();
result = result.filter(model =>
model.model_name.toLowerCase().includes(searchTerm)
);
}
return result;
}, [activeKey, models, filteredValue]);
// 搜索和操作区组件
const SearchAndActions = useMemo(() => (
<Card className="!rounded-xl mb-6" shadows='hover'>
<div className="flex flex-wrap items-center gap-4">
<div className="flex-1 min-w-[200px]">
<Input <Input
prefix={<IconSearch />}
placeholder={t('模糊搜索模型名称')} placeholder={t('模糊搜索模型名称')}
style={{ width: 200 }} className="!rounded-lg"
onCompositionStart={handleCompositionStart} onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd} onCompositionEnd={handleCompositionEnd}
onChange={handleChange} onChange={handleChange}
showClear showClear
size="large"
/> />
<Button </div>
theme='light' <Button
type='tertiary' theme='light'
style={{ width: 150 }} type='primary'
onClick={() => { icon={<IconCopy />}
copyText(selectedRowKeys); onClick={() => copyText(selectedRowKeys)}
}} disabled={selectedRowKeys.length === 0}
disabled={selectedRowKeys == ''} className="!rounded-lg !bg-blue-500 hover:!bg-blue-600 text-white"
> size="large"
{t('复制选中模型')} >
</Button> {t('复制选中模型')}
</Space> </Button>
<Table </div>
style={{ marginTop: 5 }} </Card>
columns={columns} ), [selectedRowKeys, t]);
dataSource={models}
loading={loading} // 表格组件
pagination={{ const ModelTable = useMemo(() => (
formatPageText: (page) => <Card className="!rounded-xl overflow-hidden" shadows='hover'>
t('第 {{start}} - {{end}} 条,共 {{total}} 条', { <Table
start: page.currentStart, columns={columns}
end: page.currentEnd, dataSource={filteredModels}
total: models.length, loading={loading}
}), rowSelection={rowSelection}
pageSize: models.length, className="custom-table"
showSizeChanger: false, pagination={{
}} defaultPageSize: 10,
rowSelection={rowSelection} pageSize: pageSize,
/> showSizeChanger: true,
<ImagePreview pageSizeOptions: [10, 20, 50, 100],
src={modalImageUrl} formatPageText: (page) =>
visible={isModalOpenurl} t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
onVisibleChange={(visible) => setIsModalOpenurl(visible)} start: page.currentStart,
/> end: page.currentEnd,
total: filteredModels.length,
}),
onPageSizeChange: (size) => setPageSize(size),
}}
/>
</Card>
), [filteredModels, loading, columns, rowSelection, pageSize, t]);
return (
<div className="min-h-screen bg-gray-50">
<Layout>
<Layout.Content>
<div className="flex justify-center p-4 sm:p-6 md:p-8">
<div className="w-full">
{/* 主卡片容器 */}
<Card className="!rounded-2xl shadow-lg border-0">
{/* 顶部状态卡片 */}
<Card
className="!rounded-2xl !border-0 !shadow-2xl overflow-hidden mb-6"
style={{
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 25%, #a855f7 50%, #c084fc 75%, #d8b4fe 100%)',
position: 'relative'
}}
bodyStyle={{ padding: 0 }}
>
{/* 装饰性背景元素 */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full"></div>
<div className="absolute -bottom-16 -left-16 w-48 h-48 bg-white opacity-3 rounded-full"></div>
<div className="absolute top-1/2 right-1/4 w-24 h-24 bg-yellow-400 opacity-10 rounded-full"></div>
</div>
<div className="relative p-6 sm:p-8" style={{ color: 'white' }}>
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4 lg:gap-6">
<div className="flex items-start">
<div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-white/10 flex items-center justify-center mr-3 sm:mr-4">
<Settings size={20} className="text-white" />
</div>
<div className="flex-1 min-w-0">
<div className="text-base sm:text-lg font-semibold mb-1 sm:mb-2">
{t('模型定价')}
</div>
<div className="text-sm text-white/80">
{userState.user ? (
<div className="flex items-center">
<IconVerify className="mr-1.5 flex-shrink-0" size="small" />
<span className="truncate">
{t('当前分组')}: {userState.user.group}{t('倍率')}: {groupRatio[userState.user.group]}
</span>
</div>
) : (
<div className="flex items-center">
<AlertCircle size={14} className="mr-1.5 flex-shrink-0" />
<span className="truncate">
{t('未登录,使用默认分组倍率')}: {groupRatio['default']}
</span>
</div>
)}
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-2 sm:gap-3 mt-2 lg:mt-0">
<div
className="text-center px-2 py-2 sm:px-3 sm:py-2.5 bg-white/10 rounded-lg backdrop-blur-sm hover:bg-white/20 transition-colors duration-200"
style={{ backdropFilter: 'blur(10px)' }}
>
<div className="text-xs text-white/70 mb-0.5">{t('分组倍率')}</div>
<div className="text-sm sm:text-base font-semibold">{groupRatio[selectedGroup] || '1.0'}x</div>
</div>
<div
className="text-center px-2 py-2 sm:px-3 sm:py-2.5 bg-white/10 rounded-lg backdrop-blur-sm hover:bg-white/20 transition-colors duration-200"
style={{ backdropFilter: 'blur(10px)' }}
>
<div className="text-xs text-white/70 mb-0.5">{t('可用模型')}</div>
<div className="text-sm sm:text-base font-semibold">
{models.filter(m => m.enable_groups.includes(selectedGroup)).length}
</div>
</div>
<div
className="text-center px-2 py-2 sm:px-3 sm:py-2.5 bg-white/10 rounded-lg backdrop-blur-sm hover:bg-white/20 transition-colors duration-200"
style={{ backdropFilter: 'blur(10px)' }}
>
<div className="text-xs text-white/70 mb-0.5">{t('计费类型')}</div>
<div className="text-sm sm:text-base font-semibold">2</div>
</div>
</div>
</div>
{/* 计费说明 */}
<div className="mt-4 sm:mt-5">
<div className="flex items-start">
<div
className="w-full flex items-start space-x-2 px-3 py-2 sm:px-4 sm:py-2.5 rounded-lg text-xs sm:text-sm"
style={{
backgroundColor: 'rgba(255, 255, 255, 0.2)',
color: 'white',
backdropFilter: 'blur(10px)'
}}
>
<IconInfoCircle className="flex-shrink-0 mt-0.5" size="small" />
<span>
{t('按量计费费用 = 分组倍率 × 模型倍率 × 提示token数 + 补全token数 × 补全倍率)/ 500000 (单位:美元)')}
</span>
</div>
</div>
</div>
<div className="absolute top-0 left-0 w-full h-2 bg-gradient-to-r from-yellow-400 via-orange-400 to-red-400" style={{ opacity: 0.6 }}></div>
</div>
</Card>
{/* 模型分类 Tabs */}
<div className="mb-6">
{renderTabs()}
{/* 搜索和表格区域 */}
{SearchAndActions}
{ModelTable}
</div>
{/* 倍率说明图预览 */}
<ImagePreview
src={modalImageUrl}
visible={isModalOpenurl}
onVisibleChange={(visible) => setIsModalOpenurl(visible)}
/>
</Card>
</div>
</div>
</Layout.Content>
</Layout> </Layout>
</> </div>
); );
}; };

View File

@@ -1,4 +1,5 @@
export * from './toast.constants';
export * from './user.constants';
export * from './common.constant';
export * from './channel.constants'; export * from './channel.constants';
export * from './user.constants';
export * from './toast.constants';
export * from './common.constant';
export * from './model.constants';

View File

@@ -0,0 +1,145 @@
import {
OpenAI,
Claude,
Gemini,
Moonshot,
Zhipu,
Qwen,
DeepSeek,
Minimax,
Wenxin,
Spark,
Midjourney,
Hunyuan,
Cohere,
Cloudflare,
Ai360,
Yi,
Jina,
Mistral,
XAI,
Ollama,
Doubao,
} from '@lobehub/icons';
export const MODEL_CATEGORIES = (t) => ({
all: {
label: t('全部模型'),
icon: null,
filter: () => true
},
openai: {
label: 'OpenAI',
icon: <OpenAI />,
filter: (model) => model.model_name.toLowerCase().includes('gpt') ||
model.model_name.toLowerCase().includes('dall-e') ||
model.model_name.toLowerCase().includes('whisper') ||
model.model_name.toLowerCase().includes('tts') ||
model.model_name.toLowerCase().includes('text-') ||
model.model_name.toLowerCase().includes('babbage') ||
model.model_name.toLowerCase().includes('davinci') ||
model.model_name.toLowerCase().includes('curie') ||
model.model_name.toLowerCase().includes('ada')
},
anthropic: {
label: 'Anthropic',
icon: <Claude.Color />,
filter: (model) => model.model_name.toLowerCase().includes('claude')
},
gemini: {
label: 'Gemini',
icon: <Gemini.Color />,
filter: (model) => model.model_name.toLowerCase().includes('gemini')
},
moonshot: {
label: 'Moonshot',
icon: <Moonshot />,
filter: (model) => model.model_name.toLowerCase().includes('moonshot')
},
zhipu: {
label: t('智谱'),
icon: <Zhipu.Color />,
filter: (model) => model.model_name.toLowerCase().includes('chatglm') ||
model.model_name.toLowerCase().includes('glm-')
},
qwen: {
label: t('通义千问'),
icon: <Qwen.Color />,
filter: (model) => model.model_name.toLowerCase().includes('qwen')
},
deepseek: {
label: 'DeepSeek',
icon: <DeepSeek.Color />,
filter: (model) => model.model_name.toLowerCase().includes('deepseek')
},
minimax: {
label: 'MiniMax',
icon: <Minimax.Color />,
filter: (model) => model.model_name.toLowerCase().includes('abab')
},
baidu: {
label: t('文心一言'),
icon: <Wenxin.Color />,
filter: (model) => model.model_name.toLowerCase().includes('ernie')
},
xunfei: {
label: t('讯飞星火'),
icon: <Spark.Color />,
filter: (model) => model.model_name.toLowerCase().includes('spark')
},
midjourney: {
label: 'Midjourney',
icon: <Midjourney />,
filter: (model) => model.model_name.toLowerCase().includes('mj_')
},
tencent: {
label: t('腾讯混元'),
icon: <Hunyuan.Color />,
filter: (model) => model.model_name.toLowerCase().includes('hunyuan')
},
cohere: {
label: 'Cohere',
icon: <Cohere.Color />,
filter: (model) => model.model_name.toLowerCase().includes('command')
},
cloudflare: {
label: 'Cloudflare',
icon: <Cloudflare.Color />,
filter: (model) => model.model_name.toLowerCase().includes('@cf/')
},
ai360: {
label: t('360智脑'),
icon: <Ai360.Color />,
filter: (model) => model.model_name.toLowerCase().includes('360')
},
yi: {
label: t('零一万物'),
icon: <Yi.Color />,
filter: (model) => model.model_name.toLowerCase().includes('yi')
},
jina: {
label: 'Jina',
icon: <Jina />,
filter: (model) => model.model_name.toLowerCase().includes('jina')
},
mistral: {
label: 'Mistral AI',
icon: <Mistral.Color />,
filter: (model) => model.model_name.toLowerCase().includes('mistral')
},
xai: {
label: 'xAI',
icon: <XAI />,
filter: (model) => model.model_name.toLowerCase().includes('grok')
},
llama: {
label: 'Llama',
icon: <Ollama />,
filter: (model) => model.model_name.toLowerCase().includes('llama')
},
doubao: {
label: t('豆包'),
icon: <Doubao.Color />,
filter: (model) => model.model_name.toLowerCase().includes('doubao')
}
});

View File

@@ -1515,5 +1515,16 @@
"用户分组配置": "User group configuration", "用户分组配置": "User group configuration",
"请选择可以使用该渠道的分组,留空则不更改": "Please select the groups that can use this channel, leaving blank will not change", "请选择可以使用该渠道的分组,留空则不更改": "Please select the groups that can use this channel, leaving blank will not change",
"启用全部": "Enable all", "启用全部": "Enable all",
"禁用全部": "Disable all" "禁用全部": "Disable all",
"模型定价": "Model Pricing",
"当前分组": "Current group",
"全部模型": "All Models",
"智谱": "Zhipu AI",
"通义千问": "Qwen",
"文心一言": "ERNIE Bot",
"讯飞星火": "Spark Desk",
"腾讯混元": "Hunyuan",
"360智脑": "360 AI Brain",
"零一万物": "Yi",
"豆包": "Doubao"
} }

View File

@@ -276,3 +276,7 @@ code {
.semi-datepicker-range-input { .semi-datepicker-range-input {
border-radius: 9999px; border-radius: 9999px;
} }
.semi-tabs-content {
padding: 0 !important;
}