🚀 feat(pricing): clarify “auto” group routing chain and exclude it from price table
Detailed changes Backend • `controller/pricing.go` now includes `auto_groups` in `/api/pricing` response, sourced from `setting.AutoGroups`. Frontend • `useModelPricingData.js` – Parses `auto_groups` and exposes `autoGroups` state. • `PricingPage.jsx` → `ModelDetailSideSheet.jsx` → `ModelPricingTable.jsx` – Thread `autoGroups` through component tree. • `ModelPricingTable.jsx` – Removes deprecated `getGroupDescription` / `Tooltip`. – Filters out `auto` when building price table rows. – Renders a descriptive banner: “auto 分组调用链路 → auto → group1 → …”, clarifying fallback order without showing prices. • Minor i18n tweak: adds `auto分组调用链路` key for the banner text. Why Users were confused by the “auto” tag appearing alongside regular groups with no price. This change: 1. Makes the routing chain explicit. 2. Keeps the pricing table focused on billable groups. No breaking API changes; existing clients can ignore the new `auto_groups` field.
This commit is contained in:
@@ -45,6 +45,7 @@ func GetPricing(c *gin.Context) {
|
|||||||
"group_ratio": groupRatio,
|
"group_ratio": groupRatio,
|
||||||
"usable_group": usableGroup,
|
"usable_group": usableGroup,
|
||||||
"supported_endpoint": model.GetSupportedEndpointMap(),
|
"supported_endpoint": model.GetSupportedEndpointMap(),
|
||||||
|
"auto_groups": setting.AutoGroups,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ const PricingPage = () => {
|
|||||||
visible={pricingData.showModelDetail}
|
visible={pricingData.showModelDetail}
|
||||||
onClose={pricingData.closeModelDetail}
|
onClose={pricingData.closeModelDetail}
|
||||||
modelData={pricingData.selectedModel}
|
modelData={pricingData.selectedModel}
|
||||||
selectedGroup={pricingData.selectedGroup}
|
|
||||||
groupRatio={pricingData.groupRatio}
|
groupRatio={pricingData.groupRatio}
|
||||||
usableGroup={pricingData.usableGroup}
|
usableGroup={pricingData.usableGroup}
|
||||||
currency={pricingData.currency}
|
currency={pricingData.currency}
|
||||||
@@ -81,6 +80,7 @@ const PricingPage = () => {
|
|||||||
showRatio={allProps.showRatio}
|
showRatio={allProps.showRatio}
|
||||||
vendorsMap={pricingData.vendorsMap}
|
vendorsMap={pricingData.vendorsMap}
|
||||||
endpointMap={pricingData.endpointMap}
|
endpointMap={pricingData.endpointMap}
|
||||||
|
autoGroups={pricingData.autoGroups}
|
||||||
t={pricingData.t}
|
t={pricingData.t}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ const ModelDetailSideSheet = ({
|
|||||||
visible,
|
visible,
|
||||||
onClose,
|
onClose,
|
||||||
modelData,
|
modelData,
|
||||||
selectedGroup,
|
|
||||||
groupRatio,
|
groupRatio,
|
||||||
currency,
|
currency,
|
||||||
tokenUnit,
|
tokenUnit,
|
||||||
@@ -48,6 +47,7 @@ const ModelDetailSideSheet = ({
|
|||||||
usableGroup,
|
usableGroup,
|
||||||
vendorsMap,
|
vendorsMap,
|
||||||
endpointMap,
|
endpointMap,
|
||||||
|
autoGroups,
|
||||||
t,
|
t,
|
||||||
}) => {
|
}) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
@@ -86,13 +86,13 @@ const ModelDetailSideSheet = ({
|
|||||||
<ModelEndpoints modelData={modelData} endpointMap={endpointMap} t={t} />
|
<ModelEndpoints modelData={modelData} endpointMap={endpointMap} t={t} />
|
||||||
<ModelPricingTable
|
<ModelPricingTable
|
||||||
modelData={modelData}
|
modelData={modelData}
|
||||||
selectedGroup={selectedGroup}
|
|
||||||
groupRatio={groupRatio}
|
groupRatio={groupRatio}
|
||||||
currency={currency}
|
currency={currency}
|
||||||
tokenUnit={tokenUnit}
|
tokenUnit={tokenUnit}
|
||||||
displayPrice={displayPrice}
|
displayPrice={displayPrice}
|
||||||
showRatio={showRatio}
|
showRatio={showRatio}
|
||||||
usableGroup={usableGroup}
|
usableGroup={usableGroup}
|
||||||
|
autoGroups={autoGroups}
|
||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, Avatar, Typography, Table, Tag, Tooltip } from '@douyinfe/semi-ui';
|
import { Card, Avatar, Typography, Table, Tag } from '@douyinfe/semi-ui';
|
||||||
import { IconCoinMoneyStroked } from '@douyinfe/semi-icons';
|
import { IconCoinMoneyStroked } from '@douyinfe/semi-icons';
|
||||||
import { calculateModelPrice } from '../../../../../helpers';
|
import { calculateModelPrice } from '../../../../../helpers';
|
||||||
|
|
||||||
@@ -26,34 +26,24 @@ const { Text } = Typography;
|
|||||||
|
|
||||||
const ModelPricingTable = ({
|
const ModelPricingTable = ({
|
||||||
modelData,
|
modelData,
|
||||||
selectedGroup,
|
|
||||||
groupRatio,
|
groupRatio,
|
||||||
currency,
|
currency,
|
||||||
tokenUnit,
|
tokenUnit,
|
||||||
displayPrice,
|
displayPrice,
|
||||||
showRatio,
|
showRatio,
|
||||||
usableGroup,
|
usableGroup,
|
||||||
|
autoGroups = [],
|
||||||
t,
|
t,
|
||||||
}) => {
|
}) => {
|
||||||
// 获取分组介绍
|
|
||||||
const getGroupDescription = (groupName) => {
|
|
||||||
const descriptions = {
|
|
||||||
'default': t('默认分组,适用于普通用户'),
|
|
||||||
'ssvip': t('超级VIP分组,享受最优惠价格'),
|
|
||||||
'openai官-优质': t('OpenAI官方优质分组,最快最稳,支持o1、realtime等'),
|
|
||||||
'origin': t('企业分组,OpenAI&Claude官方原价,不升价本分组稳定性可用性'),
|
|
||||||
'vip': t('VIP分组,享受优惠价格'),
|
|
||||||
'premium': t('高级分组,稳定可靠'),
|
|
||||||
'enterprise': t('企业级分组,专业服务'),
|
|
||||||
};
|
|
||||||
return descriptions[groupName] || t('用户分组');
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderGroupPriceTable = () => {
|
const renderGroupPriceTable = () => {
|
||||||
const availableGroups = Object.keys(usableGroup || {}).filter(g => g !== '');
|
// 仅展示模型可用的分组:模型 enable_groups 与用户可用分组的交集
|
||||||
if (availableGroups.length === 0) {
|
const modelEnableGroups = Array.isArray(modelData?.enable_groups)
|
||||||
availableGroups.push('default');
|
? modelData.enable_groups
|
||||||
}
|
: [];
|
||||||
|
const availableGroups = Object.keys(usableGroup || {})
|
||||||
|
.filter(g => g !== '')
|
||||||
|
.filter(g => g !== 'auto')
|
||||||
|
.filter(g => modelEnableGroups.includes(g));
|
||||||
|
|
||||||
// 准备表格数据
|
// 准备表格数据
|
||||||
const tableData = availableGroups.map(group => {
|
const tableData = availableGroups.map(group => {
|
||||||
@@ -72,7 +62,6 @@ const ModelPricingTable = ({
|
|||||||
return {
|
return {
|
||||||
key: group,
|
key: group,
|
||||||
group: group,
|
group: group,
|
||||||
description: getGroupDescription(group),
|
|
||||||
ratio: groupRatioValue,
|
ratio: groupRatioValue,
|
||||||
billingType: modelData?.quota_type === 0 ? t('按量计费') : t('按次计费'),
|
billingType: modelData?.quota_type === 0 ? t('按量计费') : t('按次计费'),
|
||||||
inputPrice: modelData?.quota_type === 0 ? priceData.inputPrice : '-',
|
inputPrice: modelData?.quota_type === 0 ? priceData.inputPrice : '-',
|
||||||
@@ -86,12 +75,10 @@ const ModelPricingTable = ({
|
|||||||
{
|
{
|
||||||
title: t('分组'),
|
title: t('分组'),
|
||||||
dataIndex: 'group',
|
dataIndex: 'group',
|
||||||
render: (text, record) => (
|
render: (text) => (
|
||||||
<Tooltip content={record.description} position="top">
|
<Tag color="white" size="small" shape="circle">
|
||||||
<Tag color="white" size="small" shape="circle" className="cursor-help">
|
|
||||||
{text}{t('分组')}
|
{text}{t('分组')}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Tooltip>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -182,6 +169,18 @@ const ModelPricingTable = ({
|
|||||||
<div className="text-xs text-gray-600">{t('不同用户分组的价格信息')}</div>
|
<div className="text-xs text-gray-600">{t('不同用户分组的价格信息')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{autoGroups && autoGroups.length > 0 && (
|
||||||
|
<div className="flex flex-wrap items-center gap-1 mb-4">
|
||||||
|
<span className="text-sm text-gray-600">{t('auto分组调用链路')}</span>
|
||||||
|
<span className="text-sm">→</span>
|
||||||
|
{autoGroups.map((g, idx) => (
|
||||||
|
<React.Fragment key={g}>
|
||||||
|
<Tag color="white" size="small" shape="circle">{g}{t('分组')}</Tag>
|
||||||
|
{idx < autoGroups.length - 1 && <span className="text-sm">→</span>}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{renderGroupPriceTable()}
|
{renderGroupPriceTable()}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export const useModelPricingData = () => {
|
|||||||
const [groupRatio, setGroupRatio] = useState({});
|
const [groupRatio, setGroupRatio] = useState({});
|
||||||
const [usableGroup, setUsableGroup] = useState({});
|
const [usableGroup, setUsableGroup] = useState({});
|
||||||
const [endpointMap, setEndpointMap] = useState({});
|
const [endpointMap, setEndpointMap] = useState({});
|
||||||
|
const [autoGroups, setAutoGroups] = useState([]);
|
||||||
|
|
||||||
const [statusState] = useContext(StatusContext);
|
const [statusState] = useContext(StatusContext);
|
||||||
const [userState] = useContext(UserContext);
|
const [userState] = useContext(UserContext);
|
||||||
@@ -160,7 +161,7 @@ export const useModelPricingData = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
let url = '/api/pricing';
|
let url = '/api/pricing';
|
||||||
const res = await API.get(url);
|
const res = await API.get(url);
|
||||||
const { success, message, data, vendors, group_ratio, usable_group, supported_endpoint } = res.data;
|
const { success, message, data, vendors, group_ratio, usable_group, supported_endpoint, auto_groups } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setGroupRatio(group_ratio);
|
setGroupRatio(group_ratio);
|
||||||
setUsableGroup(usable_group);
|
setUsableGroup(usable_group);
|
||||||
@@ -174,6 +175,7 @@ export const useModelPricingData = () => {
|
|||||||
}
|
}
|
||||||
setVendorsMap(vendorMap);
|
setVendorsMap(vendorMap);
|
||||||
setEndpointMap(supported_endpoint || {});
|
setEndpointMap(supported_endpoint || {});
|
||||||
|
setAutoGroups(auto_groups || []);
|
||||||
setModelsFormat(data, group_ratio, vendorMap);
|
setModelsFormat(data, group_ratio, vendorMap);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
@@ -282,6 +284,7 @@ export const useModelPricingData = () => {
|
|||||||
groupRatio,
|
groupRatio,
|
||||||
usableGroup,
|
usableGroup,
|
||||||
endpointMap,
|
endpointMap,
|
||||||
|
autoGroups,
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
priceRate,
|
priceRate,
|
||||||
|
|||||||
Reference in New Issue
Block a user