mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 06:16:37 +00:00
fix(accounts-ui): add actionable continuity controls and claude deep-link
This commit is contained in:
@@ -24,12 +24,13 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { Check, Pencil, Trash2, RotateCcw } from 'lucide-react';
|
||||
import { Check, CheckCheck, Link2, Pencil, RotateCcw, Trash2, Unlink } from 'lucide-react';
|
||||
import { EditAccountContextDialog } from '@/components/account/edit-account-context-dialog';
|
||||
import {
|
||||
useSetDefaultAccount,
|
||||
useDeleteAccount,
|
||||
useResetDefaultAccount,
|
||||
useUpdateAccountContext,
|
||||
} from '@/hooks/use-accounts';
|
||||
import type { Account } from '@/lib/api-client';
|
||||
|
||||
@@ -42,6 +43,7 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
||||
const setDefaultMutation = useSetDefaultAccount();
|
||||
const deleteMutation = useDeleteAccount();
|
||||
const resetDefaultMutation = useResetDefaultAccount();
|
||||
const updateContextMutation = useUpdateAccountContext();
|
||||
const [deleteTarget, setDeleteTarget] = useState<string | null>(null);
|
||||
const [contextTarget, setContextTarget] = useState<Account | null>(null);
|
||||
|
||||
@@ -130,8 +132,14 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
||||
size: 220,
|
||||
cell: ({ row }) => {
|
||||
const isDefault = row.original.name === defaultAccount;
|
||||
const isPending = setDefaultMutation.isPending || deleteMutation.isPending;
|
||||
const isPending =
|
||||
setDefaultMutation.isPending ||
|
||||
deleteMutation.isPending ||
|
||||
updateContextMutation.isPending;
|
||||
const isCliproxy = row.original.type === 'cliproxy';
|
||||
const isShared = row.original.context_mode === 'shared';
|
||||
const hasLegacyInference =
|
||||
row.original.context_inferred || row.original.continuity_inferred;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -147,6 +155,63 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
||||
<Pencil className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
{!isCliproxy && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 px-2"
|
||||
disabled={isPending}
|
||||
onClick={() =>
|
||||
updateContextMutation.mutate({
|
||||
name: row.original.name,
|
||||
context_mode: isShared ? 'isolated' : 'shared',
|
||||
context_group: isShared ? undefined : row.original.context_group || 'default',
|
||||
continuity_mode: isShared ? undefined : 'standard',
|
||||
})
|
||||
}
|
||||
title={isShared ? 'Switch to isolated mode' : 'Switch to shared mode'}
|
||||
>
|
||||
{isShared ? (
|
||||
<>
|
||||
<Unlink className="w-3 h-3 mr-1" />
|
||||
Unlink
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Link2 className="w-3 h-3 mr-1" />
|
||||
Link
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{!isCliproxy && hasLegacyInference && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2 text-amber-700 hover:text-amber-700 hover:bg-amber-500/10 dark:text-amber-400 dark:hover:text-amber-400"
|
||||
disabled={isPending}
|
||||
onClick={() =>
|
||||
updateContextMutation.mutate({
|
||||
name: row.original.name,
|
||||
context_mode: row.original.context_mode === 'shared' ? 'shared' : 'isolated',
|
||||
context_group:
|
||||
row.original.context_mode === 'shared'
|
||||
? row.original.context_group || 'default'
|
||||
: undefined,
|
||||
continuity_mode:
|
||||
row.original.context_mode === 'shared'
|
||||
? row.original.continuity_mode === 'deeper'
|
||||
? 'deeper'
|
||||
: 'standard'
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
title="Confirm this legacy account's current mode as explicit"
|
||||
>
|
||||
<CheckCheck className="w-3 h-3 mr-1" />
|
||||
Confirm
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant={isDefault ? 'secondary' : 'default'}
|
||||
size="sm"
|
||||
@@ -205,7 +270,7 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
||||
created: 'w-[150px]',
|
||||
last_used: 'w-[150px]',
|
||||
context: 'w-[170px]',
|
||||
actions: 'w-[220px]',
|
||||
actions: 'w-[340px]',
|
||||
}[header.id] || 'w-auto';
|
||||
|
||||
return (
|
||||
|
||||
@@ -136,3 +136,44 @@ export function useUpdateAccountContext() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useConfirmLegacyAccountPolicies() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (accounts: Account[]) => {
|
||||
const legacyTargets = accounts.filter(
|
||||
(account) => account.context_inferred || account.continuity_inferred
|
||||
);
|
||||
|
||||
for (const account of legacyTargets) {
|
||||
const isShared = account.context_mode === 'shared';
|
||||
await api.accounts.updateContext(account.name, {
|
||||
context_mode: isShared ? 'shared' : 'isolated',
|
||||
context_group: isShared ? account.context_group || 'default' : undefined,
|
||||
continuity_mode: isShared
|
||||
? account.continuity_mode === 'deeper'
|
||||
? 'deeper'
|
||||
: 'standard'
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return { updatedCount: legacyTargets.length };
|
||||
},
|
||||
onSuccess: ({ updatedCount }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['accounts'] });
|
||||
if (updatedCount > 0) {
|
||||
toast.success(
|
||||
`Confirmed explicit sync mode for ${updatedCount} legacy account${updatedCount > 1 ? 's' : ''}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.info('No legacy accounts need confirmation');
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+214
-71
@@ -5,7 +5,18 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AlertTriangle, ArrowRight, Link2, Plus, Unlink, Users, Waves, Zap } from 'lucide-react';
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowRight,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
Link2,
|
||||
Plus,
|
||||
Unlink,
|
||||
Users,
|
||||
Waves,
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
import { AccountsTable } from '@/components/account/accounts-table';
|
||||
import { CreateAuthProfileDialog } from '@/components/account/create-auth-profile-dialog';
|
||||
import { CopyButton } from '@/components/ui/copy-button';
|
||||
@@ -13,8 +24,9 @@ import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { useAccounts } from '@/hooks/use-accounts';
|
||||
import { useAccounts, useConfirmLegacyAccountPolicies } from '@/hooks/use-accounts';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
|
||||
@@ -90,7 +102,11 @@ function StrategyCard({
|
||||
export function AccountsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { data, isLoading } = useAccounts();
|
||||
const confirmLegacyMutation = useConfirmLegacyAccountPolicies();
|
||||
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
||||
const [showGuideRail, setShowGuideRail] = useState(true);
|
||||
const [guideOpen, setGuideOpen] = useState(false);
|
||||
|
||||
const authAccounts = data?.accounts || [];
|
||||
const cliproxyCount = data?.cliproxyCount || 0;
|
||||
const legacyContextCount = data?.legacyContextCount || 0;
|
||||
@@ -99,7 +115,16 @@ export function AccountsPage() {
|
||||
const sharedStandardCount = data?.sharedStandardCount || 0;
|
||||
const deeperSharedCount = data?.deeperSharedCount || 0;
|
||||
const isolatedCount = data?.isolatedCount || 0;
|
||||
const hasLegacyFollowUp = legacyContextCount > 0 || legacyContinuityCount > 0;
|
||||
|
||||
const legacyTargets = authAccounts.filter(
|
||||
(account) => account.context_inferred || account.continuity_inferred
|
||||
);
|
||||
const legacyTargetCount = legacyTargets.length;
|
||||
const hasLegacyFollowUp = legacyTargetCount > 0;
|
||||
|
||||
const handleOpenClaudePool = () => navigate('/cliproxy?provider=claude');
|
||||
const handleOpenClaudePoolAuth = () => navigate('/cliproxy?provider=claude&action=auth');
|
||||
const handleConfirmLegacy = () => confirmLegacyMutation.mutate(legacyTargets);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -128,11 +153,20 @@ export function AccountsPage() {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="justify-start"
|
||||
onClick={() => navigate('/cliproxy')}
|
||||
onClick={handleOpenClaudePool}
|
||||
>
|
||||
Open CLIProxy Claude Pool
|
||||
<ArrowRight className="w-4 h-4 ml-auto" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="justify-start"
|
||||
onClick={handleOpenClaudePoolAuth}
|
||||
>
|
||||
Authenticate Claude in Pool
|
||||
<Zap className="w-4 h-4 ml-auto" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,7 +210,7 @@ export function AccountsPage() {
|
||||
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
||||
Migration Follow-up
|
||||
</p>
|
||||
<div className="rounded-md border border-amber-500/50 bg-amber-500/10 p-3">
|
||||
<div className="rounded-md border border-amber-500/50 bg-amber-500/10 p-3 space-y-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle className="h-4 w-4 mt-0.5 text-amber-700 dark:text-amber-400 shrink-0" />
|
||||
<div className="space-y-1 text-xs">
|
||||
@@ -196,6 +230,18 @@ export function AccountsPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="w-full justify-start"
|
||||
onClick={handleConfirmLegacy}
|
||||
disabled={confirmLegacyMutation.isPending || legacyTargetCount === 0}
|
||||
>
|
||||
{confirmLegacyMutation.isPending
|
||||
? 'Confirming Legacy Accounts...'
|
||||
: `Confirm Current Policy for ${legacyTargetCount} Legacy Account${legacyTargetCount > 1 ? 's' : ''}`}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
@@ -222,19 +268,29 @@ export function AccountsPage() {
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline">ccs auth Workspace</Badge>
|
||||
<Badge variant="secondary">History Sync Controls</Badge>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-auto"
|
||||
onClick={() => setShowGuideRail((prev) => !prev)}
|
||||
>
|
||||
{showGuideRail ? 'Hide Action Center' : 'Show Action Center'}
|
||||
</Button>
|
||||
</div>
|
||||
<h2 className="mt-2 text-xl font-semibold tracking-tight">Auth Accounts</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
This table is intentionally scoped to
|
||||
<code className="mx-1 rounded bg-muted px-1 py-0.5">ccs auth</code>
|
||||
accounts. Edit each account for isolated, shared-standard, or shared-deeper
|
||||
continuity behavior.
|
||||
accounts. Use
|
||||
<code className="mx-1 rounded bg-muted px-1 py-0.5">Link</code>/
|
||||
<code className="mx-1 rounded bg-muted px-1 py-0.5">Unlink</code>
|
||||
for quick policy changes and pencil edit for advanced group/deeper settings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0 p-5 overflow-auto">
|
||||
<div className="flex-1 min-h-0 p-5 space-y-4 overflow-y-auto">
|
||||
{cliproxyCount > 0 && (
|
||||
<Alert variant="info" className="mb-4">
|
||||
<Alert variant="info">
|
||||
<Zap className="h-4 w-4" />
|
||||
<AlertTitle>CLIProxy pool accounts are managed in their own page</AlertTitle>
|
||||
<AlertDescription>
|
||||
@@ -246,15 +302,15 @@ export function AccountsPage() {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Card className="h-full flex flex-col">
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-lg">Account Matrix</CardTitle>
|
||||
<CardDescription>
|
||||
Shared total: {sharedCount}. Update sync behavior from the pencil action in each
|
||||
row.
|
||||
Shared total: {sharedCount}. Actions now include quick link/unlink plus legacy
|
||||
confirmation.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 min-h-0">
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<div className="text-muted-foreground">Loading accounts...</div>
|
||||
) : (
|
||||
@@ -265,64 +321,137 @@ export function AccountsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right guidance column */}
|
||||
<div className="w-80 shrink-0 flex flex-col bg-muted/20">
|
||||
<div className="p-4 border-b bg-background">
|
||||
<h3 className="font-semibold">Continuity Guide</h3>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Choose the lightest mode that solves your workflow.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="p-4 space-y-3">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm">Shared Standard</CardTitle>
|
||||
<CardDescription>Project workspace sync only.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-xs text-muted-foreground">
|
||||
Best default when users need continuity but want minimal coupling.
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm">Shared Deeper (Advanced)</CardTitle>
|
||||
<CardDescription>
|
||||
Adds <code>session-env</code>, <code>file-history</code>,{' '}
|
||||
<code>shell-snapshots</code>, <code>todos</code>.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-xs text-muted-foreground">
|
||||
Use only when cross-account continuity is worth stronger coupling.
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm">Quick Commands</CardTitle>
|
||||
<CardDescription>Copy and run in terminal.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="rounded-md border bg-background px-2 py-2 font-mono text-[11px] flex items-start gap-2">
|
||||
<span className="flex-1 break-all">
|
||||
ccs auth create work --context-group sprint-a --deeper-continuity
|
||||
</span>
|
||||
<CopyButton
|
||||
value="ccs auth create work --context-group sprint-a --deeper-continuity"
|
||||
size="icon"
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-md border bg-background px-2 py-2 font-mono text-[11px] flex items-start gap-2">
|
||||
<span className="flex-1 break-all">ccs cliproxy auth claude</span>
|
||||
<CopyButton value="ccs cliproxy auth claude" size="icon" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Right action center */}
|
||||
{showGuideRail ? (
|
||||
<div className="w-80 shrink-0 flex flex-col bg-muted/20">
|
||||
<div className="p-4 border-b bg-background flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="font-semibold">Action Center</h3>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
High-value actions for pool auth and legacy cleanup.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={() => setShowGuideRail(false)}>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="p-4 space-y-3">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm">Immediate Actions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-full justify-start"
|
||||
onClick={handleOpenClaudePoolAuth}
|
||||
>
|
||||
<Zap className="w-4 h-4 mr-2" />
|
||||
Authenticate Claude in Pool
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full justify-start"
|
||||
onClick={handleOpenClaudePool}
|
||||
>
|
||||
Open Claude Pool Settings
|
||||
<ArrowRight className="w-4 h-4 ml-auto" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full justify-start"
|
||||
onClick={handleConfirmLegacy}
|
||||
disabled={confirmLegacyMutation.isPending || legacyTargetCount === 0}
|
||||
>
|
||||
{confirmLegacyMutation.isPending
|
||||
? 'Confirming Legacy Policies...'
|
||||
: `Confirm Legacy Policies (${legacyTargetCount})`}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Collapsible open={guideOpen} onOpenChange={setGuideOpen}>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-auto w-full justify-between px-0 py-0"
|
||||
>
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-sm">Continuity Guide</CardTitle>
|
||||
<CardDescription className="mt-1">
|
||||
Expand only when needed.
|
||||
</CardDescription>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-4 w-4 transition-transform',
|
||||
guideOpen && 'rotate-180'
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
</CardHeader>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="space-y-3 text-xs text-muted-foreground">
|
||||
<div className="rounded-md border p-2.5">
|
||||
<p className="font-semibold text-foreground">Shared Standard</p>
|
||||
<p className="mt-1">
|
||||
Project workspace sync only. Best default for most teams.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-md border p-2.5">
|
||||
<p className="font-semibold text-foreground">Shared Deeper</p>
|
||||
<p className="mt-1">
|
||||
Adds <code>session-env</code>, <code>file-history</code>,{' '}
|
||||
<code>shell-snapshots</code>, <code>todos</code>.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-md border p-2.5">
|
||||
<p className="font-semibold text-foreground">Isolated</p>
|
||||
<p className="mt-1">No link. Best for strict separation.</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm">Quick Commands</CardTitle>
|
||||
<CardDescription>Copy and run in terminal.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="rounded-md border bg-background px-2 py-2 font-mono text-[11px] flex items-start gap-2">
|
||||
<span className="flex-1 break-all">
|
||||
ccs auth create work --context-group sprint-a --deeper-continuity
|
||||
</span>
|
||||
<CopyButton
|
||||
value="ccs auth create work --context-group sprint-a --deeper-continuity"
|
||||
size="icon"
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-md border bg-background px-2 py-2 font-mono text-[11px] flex items-start gap-2">
|
||||
<span className="flex-1 break-all">ccs cliproxy auth claude</span>
|
||||
<CopyButton value="ccs cliproxy auth claude" size="icon" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-14 shrink-0 border-l bg-muted/20 flex items-start justify-center pt-3">
|
||||
<Button variant="ghost" size="icon" onClick={() => setShowGuideRail(true)}>
|
||||
<ChevronLeft className="h-4 w-4 rotate-180" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -342,10 +471,24 @@ export function AccountsPage() {
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Create Account
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full" onClick={() => navigate('/cliproxy')}>
|
||||
<Button variant="outline" className="w-full" onClick={handleOpenClaudePool}>
|
||||
Open CLIProxy Claude Pool
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full" onClick={handleOpenClaudePoolAuth}>
|
||||
Authenticate Claude in Pool
|
||||
<Zap className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={handleConfirmLegacy}
|
||||
disabled={confirmLegacyMutation.isPending || legacyTargetCount === 0}
|
||||
>
|
||||
{confirmLegacyMutation.isPending
|
||||
? 'Confirming Legacy Policies...'
|
||||
: `Confirm Legacy Policies (${legacyTargetCount})`}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Right panel: Provider Editor with split-view (settings + code editor)
|
||||
*/
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from '@/hooks/use-cliproxy';
|
||||
import type { AuthStatus, Variant } from '@/lib/api-client';
|
||||
import { MODEL_CATALOGS } from '@/lib/model-catalogs';
|
||||
import { getProviderDisplayName, isValidProvider } from '@/lib/provider-config';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Sidebar provider item
|
||||
@@ -198,9 +199,14 @@ export function CliproxyPage() {
|
||||
const deleteMutation = useDeleteVariant();
|
||||
|
||||
// Selection state: either a provider or a variant
|
||||
// Initialize from localStorage if available
|
||||
// Initialize from URL provider deep-link, fallback to localStorage.
|
||||
const [selectedProvider, setSelectedProviderState] = useState<string | null>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const queryProvider = query.get('provider')?.trim().toLowerCase();
|
||||
if (queryProvider && isValidProvider(queryProvider)) {
|
||||
return queryProvider;
|
||||
}
|
||||
return localStorage.getItem('cliproxy-selected-provider');
|
||||
}
|
||||
return null;
|
||||
@@ -211,7 +217,25 @@ export function CliproxyPage() {
|
||||
provider: string;
|
||||
displayName: string;
|
||||
isFirstAccount: boolean;
|
||||
} | null>(null);
|
||||
} | null>(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const queryProvider = query.get('provider')?.trim().toLowerCase();
|
||||
const action = query.get('action');
|
||||
|
||||
if (action !== 'auth' || !queryProvider || !isValidProvider(queryProvider)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
provider: queryProvider,
|
||||
displayName: getProviderDisplayName(queryProvider),
|
||||
isFirstAccount: false,
|
||||
};
|
||||
});
|
||||
|
||||
const providers = useMemo(() => authData?.authStatus || [], [authData?.authStatus]);
|
||||
const isRemoteMode = authData?.source === 'remote';
|
||||
|
||||
Reference in New Issue
Block a user