🚀 perf: optimize model management APIs, unify pricing types as array, and remove redundancies
Backend - Add GetBoundChannelsByModelsMap to batch-fetch bound channels via a single JOIN (Distinct), compatible with SQLite/MySQL/PostgreSQL - Replace per-record enrichment with a single-pass enrichModels to avoid N+1 queries; compute unions for prefix/suffix/contains matches in memory - Change Model.QuotaType to QuotaTypes []int and expose quota_types in responses - Add GetModelQuotaTypes for cached O(1) lookups; exact models return a single-element array - Sort quota_types for stable output order - Remove unused code: GetModelByName, GetBoundChannels, GetBoundChannelsForModels, FindModelByNameWithRule, buildPrefixes, buildSuffixes - Clean up redundant comments, keeping concise and readable code Frontend - Models table: switch to quota_types, render multiple billing modes ([0], [1], [0,1], future values supported) - Pricing table: switch to quota_types; ratio display now checks quota_types.includes(0); array rendering for billing tags Compatibility - SQL uses standard JOIN/IN/Distinct; works across SQLite/MySQL/PostgreSQL - Lint passes; no DB schema changes (quota_types is a JSON response field only) Breaking Change - API field renamed: quota_type -> quota_types (array). Update clients accordingly.
This commit is contained in:
@@ -23,23 +23,31 @@ import { IconHelpCircle } from '@douyinfe/semi-icons';
|
||||
import { renderModelTag, stringToColor, calculateModelPrice, getLobeHubIcon } from '../../../../../helpers';
|
||||
import { renderLimitedItems, renderDescription } from '../../../../common/ui/RenderUtils';
|
||||
|
||||
function renderQuotaType(type, t) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return (
|
||||
<Tag color='teal' shape='circle'>
|
||||
{t('按次计费')}
|
||||
</Tag>
|
||||
);
|
||||
case 0:
|
||||
return (
|
||||
<Tag color='violet' shape='circle'>
|
||||
{t('按量计费')}
|
||||
</Tag>
|
||||
);
|
||||
default:
|
||||
return t('未知');
|
||||
}
|
||||
function renderQuotaTypes(types, t) {
|
||||
if (!Array.isArray(types) || types.length === 0) return '-';
|
||||
const renderOne = (type, idx) => {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return (
|
||||
<Tag key={`qt-${type}-${idx}`} color='teal' shape='circle'>
|
||||
{t('按次计费')}
|
||||
</Tag>
|
||||
);
|
||||
case 0:
|
||||
return (
|
||||
<Tag key={`qt-${type}-${idx}`} color='violet' shape='circle'>
|
||||
{t('按量计费')}
|
||||
</Tag>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Tag key={`qt-${type}-${idx}`} color='white' shape='circle'>
|
||||
{type}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
};
|
||||
return <Space wrap>{types.map((t0, idx) => renderOne(t0, idx))}</Space>;
|
||||
}
|
||||
|
||||
// Render vendor name
|
||||
@@ -122,11 +130,8 @@ export const getPricingTableColumns = ({
|
||||
|
||||
const quotaColumn = {
|
||||
title: t('计费类型'),
|
||||
dataIndex: 'quota_type',
|
||||
render: (text, record, index) => {
|
||||
return renderQuotaType(parseInt(text), t);
|
||||
},
|
||||
sorter: (a, b) => a.quota_type - b.quota_type,
|
||||
dataIndex: 'quota_types',
|
||||
render: (text, record, index) => renderQuotaTypes(text, t),
|
||||
};
|
||||
|
||||
const descriptionColumn = {
|
||||
@@ -170,11 +175,11 @@ export const getPricingTableColumns = ({
|
||||
const content = (
|
||||
<div className="space-y-1">
|
||||
<div className="text-gray-700">
|
||||
{t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
|
||||
{t('模型倍率')}:{Array.isArray(record.quota_types) && record.quota_types.includes(0) ? text : t('无')}
|
||||
</div>
|
||||
<div className="text-gray-700">
|
||||
{t('补全倍率')}:
|
||||
{record.quota_type === 0 ? completionRatio : t('无')}
|
||||
{Array.isArray(record.quota_types) && record.quota_types.includes(0) ? completionRatio : t('无')}
|
||||
</div>
|
||||
<div className="text-gray-700">
|
||||
{t('分组倍率')}:{groupRatio[selectedGroup]}
|
||||
|
||||
@@ -121,24 +121,36 @@ const renderEndpoints = (value) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Render quota type
|
||||
const renderQuotaType = (qt, t) => {
|
||||
if (qt === 1) {
|
||||
// Render quota types (array)
|
||||
const renderQuotaTypes = (arr, t) => {
|
||||
if (!Array.isArray(arr) || arr.length === 0) return '-';
|
||||
const renderOne = (qt, idx) => {
|
||||
if (qt === 1) {
|
||||
return (
|
||||
<Tag key={`${qt}-${idx}`} color='teal' size='small' shape='circle'>
|
||||
{t('按次计费')}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
if (qt === 0) {
|
||||
return (
|
||||
<Tag key={`${qt}-${idx}`} color='violet' size='small' shape='circle'>
|
||||
{t('按量计费')}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
// 未来新增模式的兜底展示
|
||||
return (
|
||||
<Tag color='teal' size='small' shape='circle'>
|
||||
{t('按次计费')}
|
||||
<Tag key={`${qt}-${idx}`} color='white' size='small' shape='circle'>
|
||||
{qt}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
if (qt === 0) {
|
||||
return (
|
||||
<Tag color='violet' size='small' shape='circle'>
|
||||
{t('按量计费')}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
// 未知
|
||||
return '-';
|
||||
};
|
||||
return (
|
||||
<Space wrap>
|
||||
{arr.map((qt, idx) => renderOne(qt, idx))}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
// Render bound channels
|
||||
@@ -303,8 +315,8 @@ export const getModelsColumns = ({
|
||||
},
|
||||
{
|
||||
title: t('计费类型'),
|
||||
dataIndex: 'quota_type',
|
||||
render: (qt) => renderQuotaType(qt, t),
|
||||
dataIndex: 'quota_types',
|
||||
render: (qts) => renderQuotaTypes(qts, t),
|
||||
},
|
||||
{
|
||||
title: t('创建时间'),
|
||||
|
||||
Reference in New Issue
Block a user