From 4ad7292700c991e1d2f8478da4d6ed33ce14982d Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 6 Jan 2026 12:18:12 -0500 Subject: [PATCH] feat(ui): add pause/resume toggle and tier badges - add pause/resume dropdown menu option in account item - display tier badges (ultra/pro/free) with color coding - show paused badge when account is paused - wire onPauseToggle through component tree Refs #282 --- .../cliproxy/provider-editor/account-item.tsx | 40 +++++++++++++++++++ .../provider-editor/accounts-section.tsx | 5 +++ .../cliproxy/provider-editor/index.tsx | 2 + .../provider-editor/model-config-tab.tsx | 3 ++ .../cliproxy/provider-editor/types.ts | 2 + ui/src/pages/cliproxy.tsx | 18 +++++++++ 6 files changed, 70 insertions(+) diff --git a/ui/src/components/cliproxy/provider-editor/account-item.tsx b/ui/src/components/cliproxy/provider-editor/account-item.tsx index 0ef2c276..e12d1a29 100644 --- a/ui/src/components/cliproxy/provider-editor/account-item.tsx +++ b/ui/src/components/cliproxy/provider-editor/account-item.tsx @@ -22,6 +22,8 @@ import { Loader2, CheckCircle2, HelpCircle, + Pause, + Play, } from 'lucide-react'; import { cn, @@ -86,6 +88,7 @@ export function AccountItem({ account, onSetDefault, onRemove, + onPauseToggle, isRemoving, privacyMode, showQuota, @@ -139,6 +142,28 @@ export function AccountItem({ Default )} + {account.tier && account.tier !== 'unknown' && ( + + {account.tier} + + )} + {account.paused && ( + + + Paused + + )} {account.lastUsedAt && (
@@ -162,6 +187,21 @@ export function AccountItem({ Set as default )} + {onPauseToggle && ( + onPauseToggle(!account.paused)}> + {account.paused ? ( + <> + + Resume account + + ) : ( + <> + + Pause account + + )} + + )} void; onSetDefault: (accountId: string) => void; onRemoveAccount: (accountId: string) => void; + onPauseToggle?: (accountId: string, paused: boolean) => void; isRemovingAccount?: boolean; privacyMode?: boolean; /** Show quota bars for accounts (only applicable for 'agy' provider) */ @@ -31,6 +32,7 @@ export function AccountsSection({ onAddAccount, onSetDefault, onRemoveAccount, + onPauseToggle, isRemovingAccount, privacyMode, showQuota, @@ -65,6 +67,9 @@ export function AccountsSection({ account={account} onSetDefault={() => onSetDefault(account.id)} onRemove={() => onRemoveAccount(account.id)} + onPauseToggle={ + onPauseToggle ? (paused) => onPauseToggle(account.id, paused) : undefined + } isRemoving={isRemovingAccount} privacyMode={privacyMode} showQuota={showQuota} diff --git a/ui/src/components/cliproxy/provider-editor/index.tsx b/ui/src/components/cliproxy/provider-editor/index.tsx index 53338e3c..1cc552fa 100644 --- a/ui/src/components/cliproxy/provider-editor/index.tsx +++ b/ui/src/components/cliproxy/provider-editor/index.tsx @@ -38,6 +38,7 @@ export function ProviderEditor({ onAddAccount, onSetDefault, onRemoveAccount, + onPauseToggle, isRemovingAccount, }: ProviderEditorProps) { const [customPresetOpen, setCustomPresetOpen] = useState(false); @@ -200,6 +201,7 @@ export function ProviderEditor({ onAddAccount={onAddAccount} onSetDefault={onSetDefault} onRemoveAccount={onRemoveAccount} + onPauseToggle={onPauseToggle} isRemovingAccount={isRemovingAccount} privacyMode={privacyMode} isRemoteMode={isRemoteMode} diff --git a/ui/src/components/cliproxy/provider-editor/model-config-tab.tsx b/ui/src/components/cliproxy/provider-editor/model-config-tab.tsx index a8552eb2..25ca0306 100644 --- a/ui/src/components/cliproxy/provider-editor/model-config-tab.tsx +++ b/ui/src/components/cliproxy/provider-editor/model-config-tab.tsx @@ -36,6 +36,7 @@ interface ModelConfigTabProps { onAddAccount: () => void; onSetDefault: (accountId: string) => void; onRemoveAccount: (accountId: string) => void; + onPauseToggle?: (accountId: string, paused: boolean) => void; isRemovingAccount?: boolean; privacyMode?: boolean; /** True if connected to remote CLIProxy (quota not available) */ @@ -60,6 +61,7 @@ export function ModelConfigTab({ onAddAccount, onSetDefault, onRemoveAccount, + onPauseToggle, isRemovingAccount, privacyMode, isRemoteMode, @@ -134,6 +136,7 @@ export function ModelConfigTab({ onAddAccount={onAddAccount} onSetDefault={onSetDefault} onRemoveAccount={onRemoveAccount} + onPauseToggle={onPauseToggle} isRemovingAccount={isRemovingAccount} privacyMode={privacyMode} showQuota={provider === 'agy' && !isRemoteMode} diff --git a/ui/src/components/cliproxy/provider-editor/types.ts b/ui/src/components/cliproxy/provider-editor/types.ts index 52e5a7d6..80010f9f 100644 --- a/ui/src/components/cliproxy/provider-editor/types.ts +++ b/ui/src/components/cliproxy/provider-editor/types.ts @@ -30,6 +30,7 @@ export interface ProviderEditorProps { onAddAccount: () => void; onSetDefault: (accountId: string) => void; onRemoveAccount: (accountId: string) => void; + onPauseToggle?: (accountId: string, paused: boolean) => void; isRemovingAccount?: boolean; } @@ -37,6 +38,7 @@ export interface AccountItemProps { account: OAuthAccount; onSetDefault: () => void; onRemove: () => void; + onPauseToggle?: (paused: boolean) => void; isRemoving?: boolean; privacyMode?: boolean; /** Show quota bar (only for 'agy' provider) */ diff --git a/ui/src/pages/cliproxy.tsx b/ui/src/pages/cliproxy.tsx index 872e3832..bd13b9dc 100644 --- a/ui/src/pages/cliproxy.tsx +++ b/ui/src/pages/cliproxy.tsx @@ -21,6 +21,8 @@ import { useCliproxyAuth, useSetDefaultAccount, useRemoveAccount, + usePauseAccount, + useResumeAccount, useDeleteVariant, } from '@/hooks/use-cliproxy'; import type { AuthStatus, Variant } from '@/lib/api-client'; @@ -179,6 +181,8 @@ export function CliproxyPage() { const { data: variantsData, isFetching } = useCliproxy(); const setDefaultMutation = useSetDefaultAccount(); const removeMutation = useRemoveAccount(); + const pauseMutation = usePauseAccount(); + const resumeMutation = useResumeAccount(); const deleteMutation = useDeleteVariant(); // Selection state: either a provider or a variant @@ -216,6 +220,14 @@ export function CliproxyPage() { queryClient.invalidateQueries({ queryKey: ['cliproxy-auth'] }); }; + const handlePauseToggle = (provider: string, accountId: string, paused: boolean) => { + if (paused) { + pauseMutation.mutate({ provider, accountId }); + } else { + resumeMutation.mutate({ provider, accountId }); + } + }; + const handleSelectProvider = (provider: string) => { setSelectedProvider(provider); setSelectedVariant(null); @@ -361,6 +373,9 @@ export function CliproxyPage() { accountId, }) } + onPauseToggle={(accountId, paused) => + handlePauseToggle(selectedVariantData.provider, accountId, paused) + } isRemovingAccount={removeMutation.isPending} /> ) : selectedStatus ? ( @@ -389,6 +404,9 @@ export function CliproxyPage() { accountId, }) } + onPauseToggle={(accountId, paused) => + handlePauseToggle(selectedStatus.provider, accountId, paused) + } isRemovingAccount={removeMutation.isPending} /> ) : (