🐛 fix: Abnormal animation of tokens (#10106)

*  feat(TokenDetail): add toggle for short/long format display of token values

*  feat(TokenDetail): enhance token display format persistence and toggle functionality

*  feat(TokenDetail): adjust popover trigger behavior for mobile and desktop

* replace localStorage with global store for token display format management

* add animation duration for token value display

*  feat: 强制重新挂载以防止在切换 token/credit 时出现不必要的动画
This commit is contained in:
sxjeru
2025-11-09 19:38:05 +08:00
committed by GitHub
parent 190b28244e
commit 129df7b888
3 changed files with 37 additions and 3 deletions
@@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';
import { Center, Flexbox } from 'react-layout-kit';
import InfoTooltip from '@/components/InfoTooltip';
import { useIsMobile } from '@/hooks/useIsMobile';
import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
@@ -27,6 +28,11 @@ interface TokenDetailProps {
const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
const { t } = useTranslation('chat');
const theme = useTheme();
const isMobile = useIsMobile();
// 使用 systemStatus 管理短格式显示状态
const isShortFormat = useGlobalStore(systemStatusSelectors.tokenDisplayFormatShort);
const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
const modelCard = useAiInfraStore(aiModelSelectors.getModelCard(model, provider));
const isShowCredit = useGlobalStore(systemStatusSelectors.isShowCredit) && !!modelCard?.pricing;
@@ -211,12 +217,32 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
</Flexbox>
}
placement={'top'}
trigger={['hover', 'click']}
trigger={isMobile ? ['click'] : ['hover']}
>
<Center gap={2} horizontal style={{ cursor: 'default' }}>
<Center
gap={2}
horizontal
onClick={(e) => {
// 移动端:让 Popover 处理点击事件
if (isMobile) return;
// 桌面端:阻止 Popover 并切换格式
e.preventDefault();
e.stopPropagation();
updateSystemStatus({ tokenDisplayFormatShort: !isShortFormat });
}}
style={{ cursor: isMobile ? 'default' : 'pointer' }}
>
<Icon icon={isShowCredit ? BadgeCent : CoinsIcon} />
<AnimatedNumber
formatter={(value) => (formatShortenNumber(value) as string).toLowerCase?.()}
duration={1500}
formatter={(value) => {
const roundedValue = Math.round(value);
if (isShortFormat) {
return (formatShortenNumber(roundedValue) as string).toLowerCase?.();
}
return new Intl.NumberFormat('en-US').format(roundedValue);
}}
// Force remount when switching between token/credit to prevent unwanted animation
// See: https://github.com/lobehub/lobe-chat/pull/10098
key={isShowCredit ? 'credit' : 'token'}
+5
View File
@@ -99,6 +99,10 @@ export interface SystemStatus {
* theme mode
*/
themeMode?: ThemeMode;
/**
* 是否使用短格式显示 token
*/
tokenDisplayFormatShort?: boolean;
zenMode?: boolean;
}
@@ -151,6 +155,7 @@ export const INITIAL_STATUS = {
showSystemRole: false,
systemRoleExpandedMap: {},
themeMode: 'auto',
tokenDisplayFormatShort: true,
zenMode: false,
} satisfies SystemStatus;
@@ -66,6 +66,8 @@ const getAgentSystemRoleExpanded =
const disabledModelProvidersSortType = (s: GlobalState) =>
s.status.disabledModelProvidersSortType || 'default';
const disabledModelsSortType = (s: GlobalState) => s.status.disabledModelsSortType || 'default';
const tokenDisplayFormatShort = (s: GlobalState) =>
s.status.tokenDisplayFormatShort !== undefined ? s.status.tokenDisplayFormatShort : true;
export const systemStatusSelectors = {
chatInputHeight,
@@ -99,5 +101,6 @@ export const systemStatusSelectors = {
showSystemRole,
systemStatus,
themeMode,
tokenDisplayFormatShort,
wideScreen,
};