diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index 180a79de..46d81c95 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -263,6 +263,7 @@ type DesktopServicesMenuProps = { showDevShutdown: boolean; isDevShutdownInFlight: boolean; onDevShutdown: () => Promise; + showPredValues: boolean; }; const DesktopServicesMenu = React.memo(function DesktopServicesMenu({ @@ -292,6 +293,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({ showDevShutdown, isDevShutdownInFlight, onDevShutdown, + showPredValues, }: DesktopServicesMenuProps) { const { t } = useI18n(); return ( @@ -468,7 +470,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({ className="h-1.5" expectedMarkerPercent={expectedMarker} /> - {paceInfo ? : null} + {paceInfo && showPredValues ? : null} ); })} @@ -512,7 +514,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({ className="h-1.5" expectedMarkerPercent={expectedMarker} /> - {paceInfo ? : null} + {paceInfo && showPredValues ? : null} ); })} @@ -701,6 +703,7 @@ export const Header: React.FC = ({ const isQuotaLoading = useQuotaStore((state) => state.isLoading); const quotaLastUpdated = useQuotaStore((state) => state.lastUpdated); const quotaDisplayMode = useQuotaStore((state) => state.displayMode); + const showPredValues = useQuotaStore((state) => state.showPredValues); const dropdownProviderIds = useQuotaStore((state) => state.dropdownProviderIds); const loadQuotaSettings = useQuotaStore((state) => state.loadSettings); const setQuotaDisplayMode = useQuotaStore((state) => state.setDisplayMode); @@ -1820,6 +1823,7 @@ export const Header: React.FC = ({ servicesTabItems={servicesTabItems} quotaLastUpdated={quotaLastUpdated} quotaDisplayMode={quotaDisplayMode} + showPredValues={showPredValues} quotaDisplayTabItems={quotaDisplayTabItems} handleDisplayModeChange={handleDisplayModeChange} handleUsageRefresh={handleUsageRefresh} @@ -2288,7 +2292,7 @@ export const Header: React.FC = ({ className="h-1.5" expectedMarkerPercent={expectedMarker} /> - {paceInfo ? ( + {paceInfo && showPredValues ? ( ) : null} @@ -2345,7 +2349,7 @@ export const Header: React.FC = ({ className="h-1.5" expectedMarkerPercent={expectedMarker} /> - {paceInfo ? ( + {paceInfo && showPredValues ? ( ) : null} diff --git a/packages/ui/src/components/layout/VSCodeLayout.tsx b/packages/ui/src/components/layout/VSCodeLayout.tsx index fc3acb92..fdd509e0 100644 --- a/packages/ui/src/components/layout/VSCodeLayout.tsx +++ b/packages/ui/src/components/layout/VSCodeLayout.tsx @@ -582,6 +582,7 @@ const VSCodeHeader: React.FC = ({ title, showBack, onBack, on const isQuotaLoading = useQuotaStore((state) => state.isLoading); const quotaLastUpdated = useQuotaStore((state) => state.lastUpdated); const quotaDisplayMode = useQuotaStore((state) => state.displayMode); + const showPredValues = useQuotaStore((state) => state.showPredValues); const dropdownProviderIds = useQuotaStore((state) => state.dropdownProviderIds); const loadQuotaSettings = useQuotaStore((state) => state.loadSettings); const setQuotaDisplayMode = useQuotaStore((state) => state.setDisplayMode); @@ -892,7 +893,7 @@ const VSCodeHeader: React.FC = ({ title, showBack, onBack, on className="h-1" expectedMarkerPercent={expectedMarker} /> - {paceInfo && ( + {paceInfo && showPredValues && (
diff --git a/packages/ui/src/components/sections/usage/UsageCard.tsx b/packages/ui/src/components/sections/usage/UsageCard.tsx index 96f642a7..0e3a9f85 100644 --- a/packages/ui/src/components/sections/usage/UsageCard.tsx +++ b/packages/ui/src/components/sections/usage/UsageCard.tsx @@ -24,6 +24,7 @@ export const UsageCard: React.FC = ({ onToggle, }) => { const displayMode = useQuotaStore((state) => state.displayMode); + const showPredValues = useQuotaStore((state) => state.showPredValues); const displayPercent = displayMode === 'remaining' ? window.remainingPercent : window.usedPercent; const barLabel = displayMode === 'remaining' ? 'remaining' : 'used'; const percentLabel = formatQuotaValueLabel(window.valueLabel, displayPercent); @@ -82,7 +83,7 @@ export const UsageCard: React.FC = ({ - {paceInfo && ( + {paceInfo && showPredValues && (
diff --git a/packages/ui/src/components/sections/usage/UsageSidebar.tsx b/packages/ui/src/components/sections/usage/UsageSidebar.tsx index f2d9d86b..0a4a647f 100644 --- a/packages/ui/src/components/sections/usage/UsageSidebar.tsx +++ b/packages/ui/src/components/sections/usage/UsageSidebar.tsx @@ -40,13 +40,15 @@ export const UsageSidebar: React.FC = ({ onItemSelect }) => { const setUsageAutoRefresh = useQuotaStore((state) => state.setAutoRefresh); const setUsageRefreshInterval = useQuotaStore((state) => state.setRefreshInterval); const setUsageDisplayMode = useQuotaStore((state) => state.setDisplayMode); + const showPredValues = useQuotaStore((state) => state.showPredValues); + const setShowPredValues = useQuotaStore((state) => state.setShowPredValues); const loadUsageSettings = useQuotaStore((state) => state.loadSettings); React.useEffect(() => { void loadUsageSettings(); }, [loadUsageSettings]); - const persistUsageSettings = React.useCallback(async (changes: { usageAutoRefresh?: boolean; usageRefreshIntervalMs?: number; usageDisplayMode?: 'usage' | 'remaining'; usageDropdownProviders?: string[] }) => { + const persistUsageSettings = React.useCallback(async (changes: { usageAutoRefresh?: boolean; usageRefreshIntervalMs?: number; usageDisplayMode?: 'usage' | 'remaining'; usageDropdownProviders?: string[]; usageShowPredValues?: boolean }) => { try { await updateDesktopSettings(changes); } catch (error) { @@ -76,6 +78,11 @@ export const UsageSidebar: React.FC = ({ onItemSelect }) => { void persistUsageSettings({ usageDisplayMode: value }); }, [persistUsageSettings, setUsageDisplayMode]); + const handleShowPredValuesChange = React.useCallback((enabled: boolean) => { + setShowPredValues(enabled); + void persistUsageSettings({ usageShowPredValues: enabled }); + }, [persistUsageSettings, setShowPredValues]); + const bgClass = 'bg-background'; return ( @@ -137,6 +144,16 @@ export const UsageSidebar: React.FC = ({ onItemSelect }) => { +
+ + {t('settings.usage.sidebar.field.showPredictions')} + + +
diff --git a/packages/ui/src/lib/desktop.ts b/packages/ui/src/lib/desktop.ts index 88006ae7..82568132 100644 --- a/packages/ui/src/lib/desktop.ts +++ b/packages/ui/src/lib/desktop.ts @@ -87,6 +87,7 @@ export type DesktopSettings = { usageAutoRefresh?: boolean; usageRefreshIntervalMs?: number; usageDisplayMode?: 'usage' | 'remaining'; + usageShowPredValues?: boolean; usageDropdownProviders?: string[]; usageSelectedModels?: Record; // Map of providerId -> selected model names usageCollapsedFamilies?: Record; // Map of providerId -> collapsed family IDs (UsagePage) diff --git a/packages/ui/src/lib/i18n/messages/en.settings.ts b/packages/ui/src/lib/i18n/messages/en.settings.ts index 0438616c..5c12dc1a 100644 --- a/packages/ui/src/lib/i18n/messages/en.settings.ts +++ b/packages/ui/src/lib/i18n/messages/en.settings.ts @@ -945,6 +945,7 @@ export const settingsDict = { 'settings.usage.sidebar.field.displayModePlaceholder': 'Display mode', 'settings.usage.sidebar.field.displayModeUsage': 'Usage', 'settings.usage.sidebar.field.displayModeRemaining': 'Quota remaining', + 'settings.usage.sidebar.field.showPredictions': 'Show predictions', 'settings.usage.sidebar.status.notSet': 'Not set', 'settings.usage.page.empty.selectProvider': 'Select a provider to view usage details.', 'settings.usage.page.header.providerUsage': '{provider} Usage', diff --git a/packages/ui/src/lib/i18n/messages/es.settings.ts b/packages/ui/src/lib/i18n/messages/es.settings.ts index bfcb0b42..7712375b 100644 --- a/packages/ui/src/lib/i18n/messages/es.settings.ts +++ b/packages/ui/src/lib/i18n/messages/es.settings.ts @@ -912,6 +912,7 @@ export const settingsDict = { "settings.usage.sidebar.field.displayModePlaceholder": "Modo de visualización", "settings.usage.sidebar.field.displayModeUsage": "Uso", "settings.usage.sidebar.field.displayModeRemaining": "Cuota restante", + "settings.usage.sidebar.field.showPredictions": "Mostrar predicciones", "settings.usage.sidebar.status.notSet": "No establecido", "settings.usage.page.empty.selectProvider": "Selecciona un proveedor para ver detalles de uso.", "settings.usage.page.header.providerUsage": "Uso de {provider}", diff --git a/packages/ui/src/lib/i18n/messages/ko.settings.ts b/packages/ui/src/lib/i18n/messages/ko.settings.ts index b9dd6e87..c2dfa825 100644 --- a/packages/ui/src/lib/i18n/messages/ko.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ko.settings.ts @@ -912,6 +912,7 @@ export const settingsDict = { 'settings.usage.sidebar.field.displayModePlaceholder': '표시 모드', 'settings.usage.sidebar.field.displayModeUsage': '사용량', 'settings.usage.sidebar.field.displayModeRemaining': '남은 할당량', + 'settings.usage.sidebar.field.showPredictions': '예측 표시', 'settings.usage.sidebar.status.notSet': '설정 안 됨', 'settings.usage.page.empty.selectProvider': '사용량 세부 정보를 보려면 프로바이더를 선택하세요.', 'settings.usage.page.header.providerUsage': '{provider} 사용량', diff --git a/packages/ui/src/lib/i18n/messages/pl.settings.ts b/packages/ui/src/lib/i18n/messages/pl.settings.ts index fa336fe8..e713698c 100644 --- a/packages/ui/src/lib/i18n/messages/pl.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pl.settings.ts @@ -1597,6 +1597,7 @@ export const settingsDict = { 'settings.usage.sidebar.field.display': 'Wyświetlanie', 'settings.usage.sidebar.field.displayModePlaceholder': 'Tryb wyświetlania', 'settings.usage.sidebar.field.displayModeRemaining': 'Pozostała kwota', + 'settings.usage.sidebar.field.showPredictions': 'Pokaż prognozy', 'settings.usage.sidebar.field.displayModeUsage': 'Użycie', 'settings.usage.sidebar.field.intervalPlaceholder': 'Interwał', 'settings.usage.sidebar.status.notSet': 'Nie ustawiono', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts index e93eca4d..972e62d1 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts @@ -912,6 +912,7 @@ export const settingsDict = { "settings.usage.sidebar.field.displayModePlaceholder": "Modo de visualização", "settings.usage.sidebar.field.displayModeUsage": "Uso", "settings.usage.sidebar.field.displayModeRemaining": "Cota restante", + "settings.usage.sidebar.field.showPredictions": "Mostrar previsões", "settings.usage.sidebar.status.notSet": "Não definido", "settings.usage.page.empty.selectProvider": "Selecione um provedor para ver detalhes de uso.", "settings.usage.page.header.providerUsage": "Uso de {provider}", diff --git a/packages/ui/src/lib/i18n/messages/uk.settings.ts b/packages/ui/src/lib/i18n/messages/uk.settings.ts index 0ed061de..d71a5eec 100644 --- a/packages/ui/src/lib/i18n/messages/uk.settings.ts +++ b/packages/ui/src/lib/i18n/messages/uk.settings.ts @@ -912,6 +912,7 @@ export const settingsDict = { "settings.usage.sidebar.field.displayModePlaceholder": "Режим відображення", "settings.usage.sidebar.field.displayModeUsage": "Використання", "settings.usage.sidebar.field.displayModeRemaining": "Залишок квоти", + "settings.usage.sidebar.field.showPredictions": "Показувати прогнози", "settings.usage.sidebar.status.notSet": "Не встановлено", "settings.usage.page.empty.selectProvider": "Виберіть провайдера, щоб переглянути деталі використання.", "settings.usage.page.header.providerUsage": "Використання {provider}", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts index 2d6c7081..072c699b 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts @@ -912,6 +912,7 @@ export const settingsDict = { 'settings.usage.sidebar.field.displayModePlaceholder': '显示模式', 'settings.usage.sidebar.field.displayModeUsage': '用量', 'settings.usage.sidebar.field.displayModeRemaining': '剩余配额', + 'settings.usage.sidebar.field.showPredictions': '显示预测', 'settings.usage.sidebar.status.notSet': '未设置', 'settings.usage.page.empty.selectProvider': '选择一个提供商以查看用量详情。', 'settings.usage.page.header.providerUsage': '{provider} 用量', diff --git a/packages/ui/src/lib/persistence.ts b/packages/ui/src/lib/persistence.ts index 35037334..7e88718a 100644 --- a/packages/ui/src/lib/persistence.ts +++ b/packages/ui/src/lib/persistence.ts @@ -790,6 +790,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => { if (candidate.usageDisplayMode === 'usage' || candidate.usageDisplayMode === 'remaining') { result.usageDisplayMode = candidate.usageDisplayMode; } + if (typeof candidate.usageShowPredValues === 'boolean') { + result.usageShowPredValues = candidate.usageShowPredValues; + } if (Array.isArray(candidate.usageDropdownProviders)) { result.usageDropdownProviders = candidate.usageDropdownProviders.filter( (entry): entry is string => typeof entry === 'string' && entry.length > 0 diff --git a/packages/ui/src/stores/useQuotaStore.ts b/packages/ui/src/stores/useQuotaStore.ts index 2756210c..426136ce 100644 --- a/packages/ui/src/stores/useQuotaStore.ts +++ b/packages/ui/src/stores/useQuotaStore.ts @@ -14,6 +14,7 @@ interface QuotaSettingsState { autoRefresh: boolean; refreshIntervalMs: number; displayMode: 'usage' | 'remaining'; + showPredValues: boolean; dropdownProviderIds: QuotaProviderId[]; selectedModels: Record; // Map of providerId -> selected model names expandedFamilies: Record; // Map of providerId -> EXPANDED family IDs (header dropdown - inverted) @@ -34,6 +35,7 @@ interface QuotaStore extends QuotaSettingsState { setAutoRefresh: (enabled: boolean) => void; setRefreshInterval: (intervalMs: number) => void; setDisplayMode: (mode: 'usage' | 'remaining') => void; + setShowPredValues: (enabled: boolean) => void; setDropdownProviderIds: (providerIds: QuotaProviderId[]) => void; setSelectedModels: (providerId: string, modelNames: string[]) => void; toggleModelSelected: (providerId: string, modelName: string) => void; @@ -53,6 +55,9 @@ const parseSettings = (data: Record | null): QuotaSettingsState : DEFAULT_REFRESH_INTERVAL_MS; const displayMode = data?.usageDisplayMode === 'remaining' ? 'remaining' : 'usage'; + const showPredValues = typeof data?.usageShowPredValues === 'boolean' + ? data.usageShowPredValues + : false; const rawDropdownProviders = Array.isArray(data?.usageDropdownProviders) ? data?.usageDropdownProviders : null; @@ -88,6 +93,7 @@ const parseSettings = (data: Record | null): QuotaSettingsState autoRefresh, refreshIntervalMs, displayMode, + showPredValues, dropdownProviderIds, selectedModels, expandedFamilies, @@ -121,6 +127,7 @@ const loadSettingsFromRuntime = async (): Promise => { autoRefresh: false, refreshIntervalMs: DEFAULT_REFRESH_INTERVAL_MS, displayMode: 'usage', + showPredValues: false, dropdownProviderIds: QUOTA_PROVIDERS.map((provider) => provider.id), selectedModels: {}, expandedFamilies: {}, @@ -139,6 +146,7 @@ export const useQuotaStore = create()( autoRefresh: false, refreshIntervalMs: DEFAULT_REFRESH_INTERVAL_MS, displayMode: 'usage', + showPredValues: false, dropdownProviderIds: QUOTA_PROVIDERS.map((provider) => provider.id), selectedModels: {}, expandedFamilies: {}, @@ -216,6 +224,7 @@ export const useQuotaStore = create()( set({ refreshIntervalMs: clamped }); }, setDisplayMode: (mode) => set({ displayMode: mode }), + setShowPredValues: (enabled) => set({ showPredValues: enabled }), setDropdownProviderIds: (providerIds) => set({ dropdownProviderIds: providerIds }), setSelectedModels: (providerId, modelNames) => { diff --git a/packages/vscode/src/bridge-settings-runtime.ts b/packages/vscode/src/bridge-settings-runtime.ts index 7a95e508..aa4049ab 100644 --- a/packages/vscode/src/bridge-settings-runtime.ts +++ b/packages/vscode/src/bridge-settings-runtime.ts @@ -303,6 +303,10 @@ export const persistSettings = async (changes: Record, ctx?: Br delete restChanges.usageAutoRefresh; } + if (typeof restChanges.usageShowPredValues !== 'boolean') { + delete restChanges.usageShowPredValues; + } + if (typeof restChanges.usageRefreshIntervalMs === 'number' && Number.isFinite(restChanges.usageRefreshIntervalMs)) { restChanges.usageRefreshIntervalMs = Math.max(30000, Math.min(300000, Math.round(restChanges.usageRefreshIntervalMs))); } else { diff --git a/packages/web/server/lib/opencode/settings-helpers.js b/packages/web/server/lib/opencode/settings-helpers.js index 8782b032..e5a365db 100644 --- a/packages/web/server/lib/opencode/settings-helpers.js +++ b/packages/web/server/lib/opencode/settings-helpers.js @@ -225,6 +225,9 @@ export const createSettingsHelpers = (dependencies) => { if (candidate.usageDisplayMode === 'usage' || candidate.usageDisplayMode === 'remaining') { result.usageDisplayMode = candidate.usageDisplayMode; } + if (typeof candidate.usageShowPredValues === 'boolean') { + result.usageShowPredValues = candidate.usageShowPredValues; + } if (Array.isArray(candidate.usageDropdownProviders)) { result.usageDropdownProviders = normalizeStringArray(candidate.usageDropdownProviders); }