mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 06:15:03 +00:00
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
This commit is contained in:
1 parent
b92a35d09b
commit
4ad7292700
6 files changed
+70
No files matched your search
@@ -22,6 +22,8 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
|
Pause,
|
||||||
|
Play,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
cn,
|
cn,
|
||||||
@@ -86,6 +88,7 @@ export function AccountItem({
|
|||||||
account,
|
account,
|
||||||
onSetDefault,
|
onSetDefault,
|
||||||
onRemove,
|
onRemove,
|
||||||
|
onPauseToggle,
|
||||||
isRemoving,
|
isRemoving,
|
||||||
privacyMode,
|
privacyMode,
|
||||||
showQuota,
|
showQuota,
|
||||||
@@ -139,6 +142,28 @@ export function AccountItem({
|
|||||||
Default
|
Default
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
{account.tier && account.tier !== 'unknown' && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={cn(
|
||||||
|
'text-[10px] h-4 px-1.5 uppercase',
|
||||||
|
account.tier === 'ultra' && 'border-purple-500 text-purple-600',
|
||||||
|
account.tier === 'pro' && 'border-blue-500 text-blue-600',
|
||||||
|
account.tier === 'free' && 'border-gray-400 text-gray-500'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{account.tier}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{account.paused && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[10px] h-4 px-1.5 border-yellow-500 text-yellow-600"
|
||||||
|
>
|
||||||
|
<Pause className="w-2 h-2 mr-0.5" />
|
||||||
|
Paused
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{account.lastUsedAt && (
|
{account.lastUsedAt && (
|
||||||
<div className="flex items-center gap-1 text-xs text-muted-foreground mt-0.5">
|
<div className="flex items-center gap-1 text-xs text-muted-foreground mt-0.5">
|
||||||
@@ -162,6 +187,21 @@ export function AccountItem({
|
|||||||
Set as default
|
Set as default
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
{onPauseToggle && (
|
||||||
|
<DropdownMenuItem onClick={() => onPauseToggle(!account.paused)}>
|
||||||
|
{account.paused ? (
|
||||||
|
<>
|
||||||
|
<Play className="w-4 h-4 mr-2" />
|
||||||
|
Resume account
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Pause className="w-4 h-4 mr-2" />
|
||||||
|
Pause account
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="text-destructive focus:text-destructive"
|
className="text-destructive focus:text-destructive"
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface AccountsSectionProps {
|
|||||||
onAddAccount: () => void;
|
onAddAccount: () => void;
|
||||||
onSetDefault: (accountId: string) => void;
|
onSetDefault: (accountId: string) => void;
|
||||||
onRemoveAccount: (accountId: string) => void;
|
onRemoveAccount: (accountId: string) => void;
|
||||||
|
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||||
isRemovingAccount?: boolean;
|
isRemovingAccount?: boolean;
|
||||||
privacyMode?: boolean;
|
privacyMode?: boolean;
|
||||||
/** Show quota bars for accounts (only applicable for 'agy' provider) */
|
/** Show quota bars for accounts (only applicable for 'agy' provider) */
|
||||||
@@ -31,6 +32,7 @@ export function AccountsSection({
|
|||||||
onAddAccount,
|
onAddAccount,
|
||||||
onSetDefault,
|
onSetDefault,
|
||||||
onRemoveAccount,
|
onRemoveAccount,
|
||||||
|
onPauseToggle,
|
||||||
isRemovingAccount,
|
isRemovingAccount,
|
||||||
privacyMode,
|
privacyMode,
|
||||||
showQuota,
|
showQuota,
|
||||||
@@ -65,6 +67,9 @@ export function AccountsSection({
|
|||||||
account={account}
|
account={account}
|
||||||
onSetDefault={() => onSetDefault(account.id)}
|
onSetDefault={() => onSetDefault(account.id)}
|
||||||
onRemove={() => onRemoveAccount(account.id)}
|
onRemove={() => onRemoveAccount(account.id)}
|
||||||
|
onPauseToggle={
|
||||||
|
onPauseToggle ? (paused) => onPauseToggle(account.id, paused) : undefined
|
||||||
|
}
|
||||||
isRemoving={isRemovingAccount}
|
isRemoving={isRemovingAccount}
|
||||||
privacyMode={privacyMode}
|
privacyMode={privacyMode}
|
||||||
showQuota={showQuota}
|
showQuota={showQuota}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export function ProviderEditor({
|
|||||||
onAddAccount,
|
onAddAccount,
|
||||||
onSetDefault,
|
onSetDefault,
|
||||||
onRemoveAccount,
|
onRemoveAccount,
|
||||||
|
onPauseToggle,
|
||||||
isRemovingAccount,
|
isRemovingAccount,
|
||||||
}: ProviderEditorProps) {
|
}: ProviderEditorProps) {
|
||||||
const [customPresetOpen, setCustomPresetOpen] = useState(false);
|
const [customPresetOpen, setCustomPresetOpen] = useState(false);
|
||||||
@@ -200,6 +201,7 @@ export function ProviderEditor({
|
|||||||
onAddAccount={onAddAccount}
|
onAddAccount={onAddAccount}
|
||||||
onSetDefault={onSetDefault}
|
onSetDefault={onSetDefault}
|
||||||
onRemoveAccount={onRemoveAccount}
|
onRemoveAccount={onRemoveAccount}
|
||||||
|
onPauseToggle={onPauseToggle}
|
||||||
isRemovingAccount={isRemovingAccount}
|
isRemovingAccount={isRemovingAccount}
|
||||||
privacyMode={privacyMode}
|
privacyMode={privacyMode}
|
||||||
isRemoteMode={isRemoteMode}
|
isRemoteMode={isRemoteMode}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ interface ModelConfigTabProps {
|
|||||||
onAddAccount: () => void;
|
onAddAccount: () => void;
|
||||||
onSetDefault: (accountId: string) => void;
|
onSetDefault: (accountId: string) => void;
|
||||||
onRemoveAccount: (accountId: string) => void;
|
onRemoveAccount: (accountId: string) => void;
|
||||||
|
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||||
isRemovingAccount?: boolean;
|
isRemovingAccount?: boolean;
|
||||||
privacyMode?: boolean;
|
privacyMode?: boolean;
|
||||||
/** True if connected to remote CLIProxy (quota not available) */
|
/** True if connected to remote CLIProxy (quota not available) */
|
||||||
@@ -60,6 +61,7 @@ export function ModelConfigTab({
|
|||||||
onAddAccount,
|
onAddAccount,
|
||||||
onSetDefault,
|
onSetDefault,
|
||||||
onRemoveAccount,
|
onRemoveAccount,
|
||||||
|
onPauseToggle,
|
||||||
isRemovingAccount,
|
isRemovingAccount,
|
||||||
privacyMode,
|
privacyMode,
|
||||||
isRemoteMode,
|
isRemoteMode,
|
||||||
@@ -134,6 +136,7 @@ export function ModelConfigTab({
|
|||||||
onAddAccount={onAddAccount}
|
onAddAccount={onAddAccount}
|
||||||
onSetDefault={onSetDefault}
|
onSetDefault={onSetDefault}
|
||||||
onRemoveAccount={onRemoveAccount}
|
onRemoveAccount={onRemoveAccount}
|
||||||
|
onPauseToggle={onPauseToggle}
|
||||||
isRemovingAccount={isRemovingAccount}
|
isRemovingAccount={isRemovingAccount}
|
||||||
privacyMode={privacyMode}
|
privacyMode={privacyMode}
|
||||||
showQuota={provider === 'agy' && !isRemoteMode}
|
showQuota={provider === 'agy' && !isRemoteMode}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface ProviderEditorProps {
|
|||||||
onAddAccount: () => void;
|
onAddAccount: () => void;
|
||||||
onSetDefault: (accountId: string) => void;
|
onSetDefault: (accountId: string) => void;
|
||||||
onRemoveAccount: (accountId: string) => void;
|
onRemoveAccount: (accountId: string) => void;
|
||||||
|
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||||
isRemovingAccount?: boolean;
|
isRemovingAccount?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ export interface AccountItemProps {
|
|||||||
account: OAuthAccount;
|
account: OAuthAccount;
|
||||||
onSetDefault: () => void;
|
onSetDefault: () => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
|
onPauseToggle?: (paused: boolean) => void;
|
||||||
isRemoving?: boolean;
|
isRemoving?: boolean;
|
||||||
privacyMode?: boolean;
|
privacyMode?: boolean;
|
||||||
/** Show quota bar (only for 'agy' provider) */
|
/** Show quota bar (only for 'agy' provider) */
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import {
|
|||||||
useCliproxyAuth,
|
useCliproxyAuth,
|
||||||
useSetDefaultAccount,
|
useSetDefaultAccount,
|
||||||
useRemoveAccount,
|
useRemoveAccount,
|
||||||
|
usePauseAccount,
|
||||||
|
useResumeAccount,
|
||||||
useDeleteVariant,
|
useDeleteVariant,
|
||||||
} from '@/hooks/use-cliproxy';
|
} from '@/hooks/use-cliproxy';
|
||||||
import type { AuthStatus, Variant } from '@/lib/api-client';
|
import type { AuthStatus, Variant } from '@/lib/api-client';
|
||||||
@@ -179,6 +181,8 @@ export function CliproxyPage() {
|
|||||||
const { data: variantsData, isFetching } = useCliproxy();
|
const { data: variantsData, isFetching } = useCliproxy();
|
||||||
const setDefaultMutation = useSetDefaultAccount();
|
const setDefaultMutation = useSetDefaultAccount();
|
||||||
const removeMutation = useRemoveAccount();
|
const removeMutation = useRemoveAccount();
|
||||||
|
const pauseMutation = usePauseAccount();
|
||||||
|
const resumeMutation = useResumeAccount();
|
||||||
const deleteMutation = useDeleteVariant();
|
const deleteMutation = useDeleteVariant();
|
||||||
|
|
||||||
// Selection state: either a provider or a variant
|
// Selection state: either a provider or a variant
|
||||||
@@ -216,6 +220,14 @@ export function CliproxyPage() {
|
|||||||
queryClient.invalidateQueries({ queryKey: ['cliproxy-auth'] });
|
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) => {
|
const handleSelectProvider = (provider: string) => {
|
||||||
setSelectedProvider(provider);
|
setSelectedProvider(provider);
|
||||||
setSelectedVariant(null);
|
setSelectedVariant(null);
|
||||||
@@ -361,6 +373,9 @@ export function CliproxyPage() {
|
|||||||
accountId,
|
accountId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
onPauseToggle={(accountId, paused) =>
|
||||||
|
handlePauseToggle(selectedVariantData.provider, accountId, paused)
|
||||||
|
}
|
||||||
isRemovingAccount={removeMutation.isPending}
|
isRemovingAccount={removeMutation.isPending}
|
||||||
/>
|
/>
|
||||||
) : selectedStatus ? (
|
) : selectedStatus ? (
|
||||||
@@ -389,6 +404,9 @@ export function CliproxyPage() {
|
|||||||
accountId,
|
accountId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
onPauseToggle={(accountId, paused) =>
|
||||||
|
handlePauseToggle(selectedStatus.provider, accountId, paused)
|
||||||
|
}
|
||||||
isRemovingAccount={removeMutation.isPending}
|
isRemovingAccount={removeMutation.isPending}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in new issue
Block a user