feat(billing): on-card charge/pay badge slots for models & instance types

Add the plugin-fed billing price-badge slots to the model cards and the
gpu-instance-type / storage cards so the enterprise plugin can surface
charge / pay indicators inline.
This commit is contained in:
Yuxing Deng
2026-07-22 09:46:11 +08:00
committed by jialin
parent ba0b2bb2c3
commit 5ee62f267c
4 changed files with 60 additions and 13 deletions
@@ -1,3 +1,4 @@
import PluginExtraFields from '@/components/plugin-extra-fields';
import { AutoTooltip, IconFont, ThemeTag } from '@gpustack/core-ui'; import { AutoTooltip, IconFont, ThemeTag } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Flex, Tag } from 'antd'; import { Flex, Tag } from 'antd';
@@ -271,6 +272,10 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
{cpuManufacturer} {cpuManufacturer}
</ThemeTag> </ThemeTag>
)} )}
<PluginExtraFields
name="InstanceTypeBillingBadge"
context={{ instanceType: item }}
/>
</Flex> </Flex>
</Title> </Title>
<InstanceMetadataSection spec={specData}></InstanceMetadataSection> <InstanceMetadataSection spec={specData}></InstanceMetadataSection>
@@ -1,3 +1,4 @@
import PluginExtraFields from '@/components/plugin-extra-fields';
import { FileSkeletonRows } from '@/pages/llmodels/components/model-source/file-skeleton'; import { FileSkeletonRows } from '@/pages/llmodels/components/model-source/file-skeleton';
import { TemplateCard } from '@gpustack/core-ui'; import { TemplateCard } from '@gpustack/core-ui';
import { Empty, Flex, Spin } from 'antd'; import { Empty, Flex, Spin } from 'antd';
@@ -40,6 +41,10 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
return ( return (
<Flex orientation="vertical" gap={16}> <Flex orientation="vertical" gap={16}>
<PluginExtraFields
name="InstanceTypeBillingProvider"
context={{ instanceTypes: dataList }}
/>
{dataList.map((item) => { {dataList.map((item) => {
const name = item.name; const name = item.name;
return ( return (
+45 -13
View File
@@ -1,12 +1,13 @@
import PluginExtraFields from '@/components/plugin-extra-fields';
import { import {
IconFont, IconFont,
StatusTag, StatusDot,
TagsWrapper, TagsWrapper,
TemplateCard, TemplateCard,
ThemeTag ThemeTag
} from '@gpustack/core-ui'; } from '@gpustack/core-ui';
import { useIntl, useNavigate } from '@umijs/max'; import { useIntl, useNavigate } from '@umijs/max';
import { Button } from 'antd'; import { Button, Tooltip } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -192,6 +193,23 @@ const ModelItem: React.FC<{
return _.round(max_tokens / 1024); return _.round(max_tokens / 1024);
}, [model]); }, [model]);
// Ready is the normal state — show just the dot (no label); other states
// (Not Ready / Stopped) keep their label so the problem is legible.
const statusNode = (
<StatusDot
statusValue={{
status: MyModelsStatusMap[model.status],
text:
model.status === MyModelsStatusValueMap.Ready ||
!MyModelsStatusLabelMap[model.status]
? ''
: intl.formatMessage({
id: MyModelsStatusLabelMap[model.status]
})
}}
/>
);
return ( return (
<CardWrapper> <CardWrapper>
<TemplateCard <TemplateCard
@@ -210,18 +228,32 @@ const ModelItem: React.FC<{
) : ( ) : (
<ModelLogo src={defaultModelLogo} alt="" /> <ModelLogo src={defaultModelLogo} alt="" />
)} )}
<span>{model.name}</span> <span className="flex-center" style={{ gap: 8, minWidth: 0 }}>
<span>{model.name}</span>
{/* Status moved to a compact dot right after the name, freeing
the header's right side for the price summary. The dot keeps
the state message on hover (StatusDot has no built-in one). */}
<span
style={{
fontWeight: 400,
fontSize: 'var(--font-size-small)'
}}
>
{model.state_message ? (
<Tooltip title={model.state_message}>
<span style={{ display: 'inline-flex' }}>
{statusNode}
</span>
</Tooltip>
) : (
statusNode
)}
</span>
</span>
</span>
<span className="flex-center gap-8">
<PluginExtraFields name="ModelPriceSummary" context={{ model }} />
</span> </span>
<StatusTag
maxTooltipWidth={400}
statusValue={{
status: MyModelsStatusMap[model.status],
text: intl.formatMessage({
id: MyModelsStatusLabelMap[model.status] || ''
}),
message: model.state_message
}}
></StatusTag>
</Header> </Header>
} }
> >
+5
View File
@@ -1,3 +1,4 @@
import PluginExtraFields from '@/components/plugin-extra-fields';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import { SyncOutlined } from '@ant-design/icons'; import { SyncOutlined } from '@ant-design/icons';
import { import {
@@ -268,6 +269,10 @@ const UserModels: React.FC = () => {
</Space> </Space>
} }
></PageTools> ></PageTools>
<PluginExtraFields
name="ModelBillingProvider"
context={{ models: dataList }}
/>
<InfiniteScrollerProvider <InfiniteScrollerProvider
value={{ value={{
total: dataSource.totalPage, total: dataSource.totalPage,