mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 06:15:03 +00:00
feat(ui): add bulk account controls to provider editor
- add BulkActionBar component for multi-select pause/resume - add AccountItem with individual pause toggle and solo button - fix solo button disabled logic for consistency - add selection state and bulk operations to AccountsSection
This commit is contained in:
1 parent
708661744f
commit
1427d36f86
6 files changed
+323
-41
No files matched your search
@@ -6,6 +6,8 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -23,10 +25,10 @@ import {
|
|||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
Pause,
|
Pause,
|
||||||
Play,
|
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
FolderCode,
|
FolderCode,
|
||||||
|
Radio,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
cn,
|
cn,
|
||||||
@@ -93,10 +95,15 @@ export function AccountItem({
|
|||||||
onSetDefault,
|
onSetDefault,
|
||||||
onRemove,
|
onRemove,
|
||||||
onPauseToggle,
|
onPauseToggle,
|
||||||
|
onSoloMode,
|
||||||
isRemoving,
|
isRemoving,
|
||||||
isPausingAccount,
|
isPausingAccount,
|
||||||
|
isSoloingAccount,
|
||||||
privacyMode,
|
privacyMode,
|
||||||
showQuota,
|
showQuota,
|
||||||
|
selectable,
|
||||||
|
selected,
|
||||||
|
onSelectChange,
|
||||||
}: AccountItemProps) {
|
}: AccountItemProps) {
|
||||||
// Fetch runtime stats to get actual lastUsedAt (more accurate than file state)
|
// Fetch runtime stats to get actual lastUsedAt (more accurate than file state)
|
||||||
const { data: stats } = useCliproxyStats(showQuota);
|
const { data: stats } = useCliproxyStats(showQuota);
|
||||||
@@ -123,11 +130,20 @@ export function AccountItem({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col gap-2 p-3 rounded-lg border transition-colors',
|
'flex flex-col gap-2 p-3 rounded-lg border transition-colors',
|
||||||
account.isDefault ? 'border-primary/30 bg-primary/5' : 'border-border hover:bg-muted/30'
|
account.isDefault ? 'border-primary/30 bg-primary/5' : 'border-border hover:bg-muted/30',
|
||||||
|
account.paused && 'opacity-50 border-muted'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
{/* Multi-select checkbox */}
|
||||||
|
{selectable && (
|
||||||
|
<Checkbox
|
||||||
|
checked={selected}
|
||||||
|
onCheckedChange={(checked) => onSelectChange?.(!!checked)}
|
||||||
|
aria-label={`Select ${account.email || account.id}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center justify-center w-8 h-8 rounded-full',
|
'flex items-center justify-center w-8 h-8 rounded-full',
|
||||||
@@ -226,47 +242,80 @@ export function AccountItem({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DropdownMenu>
|
{/* Inline controls: Solo button + Toggle switch */}
|
||||||
<DropdownMenuTrigger asChild>
|
<div className="flex items-center gap-1.5">
|
||||||
<Button variant="ghost" size="icon" className="h-7 w-7">
|
{/* Solo mode button */}
|
||||||
<MoreHorizontal className="w-4 h-4" />
|
{onSoloMode && (
|
||||||
</Button>
|
<TooltipProvider>
|
||||||
</DropdownMenuTrigger>
|
<Tooltip>
|
||||||
<DropdownMenuContent align="end">
|
<TooltipTrigger asChild>
|
||||||
{!account.isDefault && (
|
<Button
|
||||||
<DropdownMenuItem onClick={onSetDefault}>
|
variant="ghost"
|
||||||
<Star className="w-4 h-4 mr-2" />
|
size="icon"
|
||||||
Set as default
|
className="h-6 w-6"
|
||||||
</DropdownMenuItem>
|
onClick={onSoloMode}
|
||||||
)}
|
disabled={isSoloingAccount || account.paused}
|
||||||
{onPauseToggle && (
|
aria-label="Activate only this account"
|
||||||
|
>
|
||||||
|
{isSoloingAccount ? (
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Radio className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Activate only this account</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Pause/Resume toggle switch */}
|
||||||
|
{onPauseToggle && (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div>
|
||||||
|
<Switch
|
||||||
|
checked={!account.paused}
|
||||||
|
onCheckedChange={(checked) => onPauseToggle(!checked)}
|
||||||
|
disabled={isPausingAccount}
|
||||||
|
aria-label={account.paused ? 'Resume account' : 'Pause account'}
|
||||||
|
className="scale-90"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{account.paused ? 'Resume account' : 'Pause account'}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Dropdown menu for other actions */}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="h-7 w-7">
|
||||||
|
<MoreHorizontal className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{!account.isDefault && (
|
||||||
|
<DropdownMenuItem onClick={onSetDefault}>
|
||||||
|
<Star className="w-4 h-4 mr-2" />
|
||||||
|
Set as default
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => onPauseToggle(!account.paused)}
|
className="text-destructive focus:text-destructive"
|
||||||
disabled={isPausingAccount}
|
onClick={onRemove}
|
||||||
|
disabled={isRemoving}
|
||||||
>
|
>
|
||||||
{account.paused ? (
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
<>
|
{isRemoving ? 'Removing...' : 'Remove account'}
|
||||||
<Play className="w-4 h-4 mr-2" />
|
|
||||||
{isPausingAccount ? 'Resuming...' : 'Resume account'}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Pause className="w-4 h-4 mr-2" />
|
|
||||||
{isPausingAccount ? 'Pausing...' : 'Pause account'}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
</DropdownMenuContent>
|
||||||
<DropdownMenuItem
|
</DropdownMenu>
|
||||||
className="text-destructive focus:text-destructive"
|
</div>
|
||||||
onClick={onRemove}
|
|
||||||
disabled={isRemoving}
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
|
||||||
{isRemoving ? 'Removing...' : 'Remove account'}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quota bar - supports all providers with quota API */}
|
{/* Quota bar - supports all providers with quota API */}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* Accounts Section Component
|
* Accounts Section Component
|
||||||
* Manages connected OAuth accounts for a provider
|
* Manages connected OAuth accounts for a provider with multi-select bulk actions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useState, useMemo, useCallback } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { User, Plus, Globe } from 'lucide-react';
|
import { User, Plus, Globe } from 'lucide-react';
|
||||||
import { AccountItem } from './account-item';
|
import { AccountItem } from './account-item';
|
||||||
|
import { BulkActionBar } from './bulk-action-bar';
|
||||||
import type { OAuthAccount } from '@/lib/api-client';
|
import type { OAuthAccount } from '@/lib/api-client';
|
||||||
|
|
||||||
interface AccountsSectionProps {
|
interface AccountsSectionProps {
|
||||||
@@ -16,9 +19,21 @@ interface AccountsSectionProps {
|
|||||||
onSetDefault: (accountId: string) => void;
|
onSetDefault: (accountId: string) => void;
|
||||||
onRemoveAccount: (accountId: string) => void;
|
onRemoveAccount: (accountId: string) => void;
|
||||||
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||||
|
/** Solo mode: activate one account, pause all others */
|
||||||
|
onSoloMode?: (accountId: string) => void;
|
||||||
|
/** Bulk pause multiple accounts */
|
||||||
|
onBulkPause?: (accountIds: string[]) => void;
|
||||||
|
/** Bulk resume multiple accounts */
|
||||||
|
onBulkResume?: (accountIds: string[]) => void;
|
||||||
isRemovingAccount?: boolean;
|
isRemovingAccount?: boolean;
|
||||||
/** Pause/resume mutation in progress */
|
/** Pause/resume mutation in progress */
|
||||||
isPausingAccount?: boolean;
|
isPausingAccount?: boolean;
|
||||||
|
/** Solo mode mutation in progress */
|
||||||
|
isSoloingAccount?: boolean;
|
||||||
|
/** Bulk pause mutation in progress */
|
||||||
|
isBulkPausing?: boolean;
|
||||||
|
/** Bulk resume mutation in progress */
|
||||||
|
isBulkResuming?: 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) */
|
||||||
showQuota?: boolean;
|
showQuota?: boolean;
|
||||||
@@ -35,8 +50,14 @@ export function AccountsSection({
|
|||||||
onSetDefault,
|
onSetDefault,
|
||||||
onRemoveAccount,
|
onRemoveAccount,
|
||||||
onPauseToggle,
|
onPauseToggle,
|
||||||
|
onSoloMode,
|
||||||
|
onBulkPause,
|
||||||
|
onBulkResume,
|
||||||
isRemovingAccount,
|
isRemovingAccount,
|
||||||
isPausingAccount,
|
isPausingAccount,
|
||||||
|
isSoloingAccount,
|
||||||
|
isBulkPausing,
|
||||||
|
isBulkResuming,
|
||||||
privacyMode,
|
privacyMode,
|
||||||
showQuota,
|
showQuota,
|
||||||
isKiro,
|
isKiro,
|
||||||
@@ -44,10 +65,80 @@ export function AccountsSection({
|
|||||||
onKiroNoIncognitoChange,
|
onKiroNoIncognitoChange,
|
||||||
kiroSettingsLoading,
|
kiroSettingsLoading,
|
||||||
}: AccountsSectionProps) {
|
}: AccountsSectionProps) {
|
||||||
|
// Multi-select state - raw selection (may contain stale IDs)
|
||||||
|
const [rawSelectedIds, setRawSelectedIds] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
// Derive valid selections by filtering out stale IDs
|
||||||
|
const accountIds = useMemo(() => new Set(accounts.map((a) => a.id)), [accounts]);
|
||||||
|
const selectedIds = useMemo(
|
||||||
|
() => new Set([...rawSelectedIds].filter((id) => accountIds.has(id))),
|
||||||
|
[rawSelectedIds, accountIds]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enable multi-select when bulk actions are available
|
||||||
|
const isSelectable = !!(onBulkPause && onBulkResume);
|
||||||
|
|
||||||
|
// Computed selection state
|
||||||
|
const selectedCount = selectedIds.size;
|
||||||
|
const allSelected = accounts.length > 0 && selectedIds.size === accounts.length;
|
||||||
|
const someSelected = selectedIds.size > 0 && selectedIds.size < accounts.length;
|
||||||
|
|
||||||
|
// Selection handlers
|
||||||
|
const toggleSelect = useCallback((id: string) => {
|
||||||
|
setRawSelectedIds((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(id)) next.delete(id);
|
||||||
|
else next.add(id);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectAll = useCallback(() => {
|
||||||
|
setRawSelectedIds(new Set(accounts.map((a) => a.id)));
|
||||||
|
}, [accounts]);
|
||||||
|
|
||||||
|
const deselectAll = useCallback(() => {
|
||||||
|
setRawSelectedIds(new Set());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleSelectAll = useCallback(() => {
|
||||||
|
if (allSelected) {
|
||||||
|
deselectAll();
|
||||||
|
} else {
|
||||||
|
selectAll();
|
||||||
|
}
|
||||||
|
}, [allSelected, selectAll, deselectAll]);
|
||||||
|
|
||||||
|
// Bulk action handlers
|
||||||
|
const handleBulkPause = useCallback(() => {
|
||||||
|
if (onBulkPause && selectedIds.size > 0) {
|
||||||
|
onBulkPause(Array.from(selectedIds));
|
||||||
|
// Clear selection after action (will be invalidated by mutation success)
|
||||||
|
setRawSelectedIds(new Set());
|
||||||
|
}
|
||||||
|
}, [onBulkPause, selectedIds]);
|
||||||
|
|
||||||
|
const handleBulkResume = useCallback(() => {
|
||||||
|
if (onBulkResume && selectedIds.size > 0) {
|
||||||
|
onBulkResume(Array.from(selectedIds));
|
||||||
|
setRawSelectedIds(new Set());
|
||||||
|
}
|
||||||
|
}, [onBulkResume, selectedIds]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h3 className="text-sm font-medium flex items-center gap-2">
|
<h3 className="text-sm font-medium flex items-center gap-2">
|
||||||
|
{/* Select All checkbox */}
|
||||||
|
{isSelectable && accounts.length > 0 && (
|
||||||
|
<Checkbox
|
||||||
|
checked={allSelected}
|
||||||
|
onCheckedChange={toggleSelectAll}
|
||||||
|
aria-label="Select all accounts"
|
||||||
|
className={someSelected ? 'data-[state=checked]:bg-primary/50' : ''}
|
||||||
|
{...(someSelected ? { 'data-state': 'indeterminate' } : {})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<User className="w-4 h-4" />
|
<User className="w-4 h-4" />
|
||||||
Accounts
|
Accounts
|
||||||
{accounts.length > 0 && (
|
{accounts.length > 0 && (
|
||||||
@@ -73,10 +164,15 @@ export function AccountsSection({
|
|||||||
onPauseToggle={
|
onPauseToggle={
|
||||||
onPauseToggle ? (paused) => onPauseToggle(account.id, paused) : undefined
|
onPauseToggle ? (paused) => onPauseToggle(account.id, paused) : undefined
|
||||||
}
|
}
|
||||||
|
onSoloMode={onSoloMode ? () => onSoloMode(account.id) : undefined}
|
||||||
isRemoving={isRemovingAccount}
|
isRemoving={isRemovingAccount}
|
||||||
isPausingAccount={isPausingAccount}
|
isPausingAccount={isPausingAccount}
|
||||||
|
isSoloingAccount={isSoloingAccount}
|
||||||
privacyMode={privacyMode}
|
privacyMode={privacyMode}
|
||||||
showQuota={showQuota}
|
showQuota={showQuota}
|
||||||
|
selectable={isSelectable}
|
||||||
|
selected={selectedIds.has(account.id)}
|
||||||
|
onSelectChange={() => toggleSelect(account.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -88,6 +184,18 @@ export function AccountsSection({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Bulk Action Bar - shows when accounts selected */}
|
||||||
|
{isSelectable && (
|
||||||
|
<BulkActionBar
|
||||||
|
selectedCount={selectedCount}
|
||||||
|
onPauseSelected={handleBulkPause}
|
||||||
|
onResumeSelected={handleBulkResume}
|
||||||
|
onClearSelection={deselectAll}
|
||||||
|
isPausing={!!isBulkPausing}
|
||||||
|
isResuming={!!isBulkResuming}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Kiro-specific: Incognito browser setting - users complain "it keeps opening incognito" */}
|
{/* Kiro-specific: Incognito browser setting - users complain "it keeps opening incognito" */}
|
||||||
{isKiro && onKiroNoIncognitoChange && (
|
{isKiro && onKiroNoIncognitoChange && (
|
||||||
<div className="mt-3 pt-3 border-t border-dashed">
|
<div className="mt-3 pt-3 border-t border-dashed">
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Bulk Action Bar Component
|
||||||
|
* Appears when accounts are selected for bulk pause/resume operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Pause, Play, Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
|
interface BulkActionBarProps {
|
||||||
|
selectedCount: number;
|
||||||
|
onPauseSelected: () => void;
|
||||||
|
onResumeSelected: () => void;
|
||||||
|
onClearSelection: () => void;
|
||||||
|
isPausing: boolean;
|
||||||
|
isResuming: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BulkActionBar({
|
||||||
|
selectedCount,
|
||||||
|
onPauseSelected,
|
||||||
|
onResumeSelected,
|
||||||
|
onClearSelection,
|
||||||
|
isPausing,
|
||||||
|
isResuming,
|
||||||
|
}: BulkActionBarProps) {
|
||||||
|
// Show bar when at least 1 account is selected (per validation decision)
|
||||||
|
if (selectedCount < 1) return null;
|
||||||
|
|
||||||
|
const isLoading = isPausing || isResuming;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-3 p-2 bg-muted/50 rounded-lg border mt-2">
|
||||||
|
<span className="text-sm text-muted-foreground">{selectedCount} selected</span>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs h-6 px-2"
|
||||||
|
onClick={onClearSelection}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
<div className="flex gap-2 ml-auto">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="h-7 text-xs gap-1"
|
||||||
|
onClick={onPauseSelected}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{isPausing ? <Loader2 className="w-3 h-3 animate-spin" /> : <Pause className="w-3 h-3" />}
|
||||||
|
Pause Selected
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="h-7 text-xs gap-1"
|
||||||
|
onClick={onResumeSelected}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{isResuming ? <Loader2 className="w-3 h-3 animate-spin" /> : <Play className="w-3 h-3" />}
|
||||||
|
Resume Selected
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -39,8 +39,14 @@ export function ProviderEditor({
|
|||||||
onSetDefault,
|
onSetDefault,
|
||||||
onRemoveAccount,
|
onRemoveAccount,
|
||||||
onPauseToggle,
|
onPauseToggle,
|
||||||
|
onSoloMode,
|
||||||
|
onBulkPause,
|
||||||
|
onBulkResume,
|
||||||
isRemovingAccount,
|
isRemovingAccount,
|
||||||
isPausingAccount,
|
isPausingAccount,
|
||||||
|
isSoloingAccount,
|
||||||
|
isBulkPausing,
|
||||||
|
isBulkResuming,
|
||||||
}: ProviderEditorProps) {
|
}: ProviderEditorProps) {
|
||||||
const [customPresetOpen, setCustomPresetOpen] = useState(false);
|
const [customPresetOpen, setCustomPresetOpen] = useState(false);
|
||||||
const { privacyMode } = usePrivacy();
|
const { privacyMode } = usePrivacy();
|
||||||
@@ -203,8 +209,14 @@ export function ProviderEditor({
|
|||||||
onSetDefault={onSetDefault}
|
onSetDefault={onSetDefault}
|
||||||
onRemoveAccount={onRemoveAccount}
|
onRemoveAccount={onRemoveAccount}
|
||||||
onPauseToggle={onPauseToggle}
|
onPauseToggle={onPauseToggle}
|
||||||
|
onSoloMode={onSoloMode}
|
||||||
|
onBulkPause={onBulkPause}
|
||||||
|
onBulkResume={onBulkResume}
|
||||||
isRemovingAccount={isRemovingAccount}
|
isRemovingAccount={isRemovingAccount}
|
||||||
isPausingAccount={isPausingAccount}
|
isPausingAccount={isPausingAccount}
|
||||||
|
isSoloingAccount={isSoloingAccount}
|
||||||
|
isBulkPausing={isBulkPausing}
|
||||||
|
isBulkResuming={isBulkResuming}
|
||||||
privacyMode={privacyMode}
|
privacyMode={privacyMode}
|
||||||
isRemoteMode={isRemoteMode}
|
isRemoteMode={isRemoteMode}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -37,9 +37,21 @@ interface ModelConfigTabProps {
|
|||||||
onSetDefault: (accountId: string) => void;
|
onSetDefault: (accountId: string) => void;
|
||||||
onRemoveAccount: (accountId: string) => void;
|
onRemoveAccount: (accountId: string) => void;
|
||||||
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||||
|
/** Solo mode: activate one account, pause all others */
|
||||||
|
onSoloMode?: (accountId: string) => void;
|
||||||
isRemovingAccount?: boolean;
|
isRemovingAccount?: boolean;
|
||||||
/** Pause/resume mutation in progress */
|
/** Pause/resume mutation in progress */
|
||||||
isPausingAccount?: boolean;
|
isPausingAccount?: boolean;
|
||||||
|
/** Solo mode mutation in progress */
|
||||||
|
isSoloingAccount?: boolean;
|
||||||
|
/** Bulk pause multiple accounts */
|
||||||
|
onBulkPause?: (accountIds: string[]) => void;
|
||||||
|
/** Bulk resume multiple accounts */
|
||||||
|
onBulkResume?: (accountIds: string[]) => void;
|
||||||
|
/** Bulk pause mutation in progress */
|
||||||
|
isBulkPausing?: boolean;
|
||||||
|
/** Bulk resume mutation in progress */
|
||||||
|
isBulkResuming?: boolean;
|
||||||
privacyMode?: boolean;
|
privacyMode?: boolean;
|
||||||
/** True if connected to remote CLIProxy (quota not available) */
|
/** True if connected to remote CLIProxy (quota not available) */
|
||||||
isRemoteMode?: boolean;
|
isRemoteMode?: boolean;
|
||||||
@@ -64,8 +76,14 @@ export function ModelConfigTab({
|
|||||||
onSetDefault,
|
onSetDefault,
|
||||||
onRemoveAccount,
|
onRemoveAccount,
|
||||||
onPauseToggle,
|
onPauseToggle,
|
||||||
|
onSoloMode,
|
||||||
|
onBulkPause,
|
||||||
|
onBulkResume,
|
||||||
isRemovingAccount,
|
isRemovingAccount,
|
||||||
isPausingAccount,
|
isPausingAccount,
|
||||||
|
isSoloingAccount,
|
||||||
|
isBulkPausing,
|
||||||
|
isBulkResuming,
|
||||||
privacyMode,
|
privacyMode,
|
||||||
isRemoteMode,
|
isRemoteMode,
|
||||||
}: ModelConfigTabProps) {
|
}: ModelConfigTabProps) {
|
||||||
@@ -140,8 +158,14 @@ export function ModelConfigTab({
|
|||||||
onSetDefault={onSetDefault}
|
onSetDefault={onSetDefault}
|
||||||
onRemoveAccount={onRemoveAccount}
|
onRemoveAccount={onRemoveAccount}
|
||||||
onPauseToggle={onPauseToggle}
|
onPauseToggle={onPauseToggle}
|
||||||
|
onSoloMode={onSoloMode}
|
||||||
|
onBulkPause={onBulkPause}
|
||||||
|
onBulkResume={onBulkResume}
|
||||||
isRemovingAccount={isRemovingAccount}
|
isRemovingAccount={isRemovingAccount}
|
||||||
isPausingAccount={isPausingAccount}
|
isPausingAccount={isPausingAccount}
|
||||||
|
isSoloingAccount={isSoloingAccount}
|
||||||
|
isBulkPausing={isBulkPausing}
|
||||||
|
isBulkResuming={isBulkResuming}
|
||||||
privacyMode={privacyMode}
|
privacyMode={privacyMode}
|
||||||
showQuota={provider === 'agy' && !isRemoteMode}
|
showQuota={provider === 'agy' && !isRemoteMode}
|
||||||
isKiro={isKiro}
|
isKiro={isKiro}
|
||||||
|
|||||||
@@ -31,9 +31,21 @@ export interface ProviderEditorProps {
|
|||||||
onSetDefault: (accountId: string) => void;
|
onSetDefault: (accountId: string) => void;
|
||||||
onRemoveAccount: (accountId: string) => void;
|
onRemoveAccount: (accountId: string) => void;
|
||||||
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||||
|
/** Solo mode: activate one account, pause all others */
|
||||||
|
onSoloMode?: (accountId: string) => void;
|
||||||
|
/** Bulk pause multiple accounts */
|
||||||
|
onBulkPause?: (accountIds: string[]) => void;
|
||||||
|
/** Bulk resume multiple accounts */
|
||||||
|
onBulkResume?: (accountIds: string[]) => void;
|
||||||
isRemovingAccount?: boolean;
|
isRemovingAccount?: boolean;
|
||||||
/** Pause/resume mutation in progress */
|
/** Pause/resume mutation in progress */
|
||||||
isPausingAccount?: boolean;
|
isPausingAccount?: boolean;
|
||||||
|
/** Solo mode mutation in progress */
|
||||||
|
isSoloingAccount?: boolean;
|
||||||
|
/** Bulk pause mutation in progress */
|
||||||
|
isBulkPausing?: boolean;
|
||||||
|
/** Bulk resume mutation in progress */
|
||||||
|
isBulkResuming?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccountItemProps {
|
export interface AccountItemProps {
|
||||||
@@ -41,12 +53,22 @@ export interface AccountItemProps {
|
|||||||
onSetDefault: () => void;
|
onSetDefault: () => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
onPauseToggle?: (paused: boolean) => void;
|
onPauseToggle?: (paused: boolean) => void;
|
||||||
|
/** Solo mode: activate this account, pause all others */
|
||||||
|
onSoloMode?: () => void;
|
||||||
isRemoving?: boolean;
|
isRemoving?: boolean;
|
||||||
/** Pause/resume mutation in progress */
|
/** Pause/resume mutation in progress */
|
||||||
isPausingAccount?: boolean;
|
isPausingAccount?: boolean;
|
||||||
|
/** Solo mode mutation in progress */
|
||||||
|
isSoloingAccount?: boolean;
|
||||||
privacyMode?: boolean;
|
privacyMode?: boolean;
|
||||||
/** Show quota bar (only for 'agy' provider) */
|
/** Show quota bar (only for 'agy' provider) */
|
||||||
showQuota?: boolean;
|
showQuota?: boolean;
|
||||||
|
/** Enable checkbox for multi-select */
|
||||||
|
selectable?: boolean;
|
||||||
|
/** Whether this account is currently selected */
|
||||||
|
selected?: boolean;
|
||||||
|
/** Called when checkbox is toggled */
|
||||||
|
onSelectChange?: (selected: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelMappingValues {
|
export interface ModelMappingValues {
|
||||||
|
|||||||
Reference in new issue
Block a user