mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 14:19:56 +00:00
fix(accounts-ui): add actionable continuity controls and claude deep-link
This commit is contained in:
@@ -24,12 +24,13 @@ import {
|
|||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from '@/components/ui/alert-dialog';
|
} 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 { EditAccountContextDialog } from '@/components/account/edit-account-context-dialog';
|
||||||
import {
|
import {
|
||||||
useSetDefaultAccount,
|
useSetDefaultAccount,
|
||||||
useDeleteAccount,
|
useDeleteAccount,
|
||||||
useResetDefaultAccount,
|
useResetDefaultAccount,
|
||||||
|
useUpdateAccountContext,
|
||||||
} from '@/hooks/use-accounts';
|
} from '@/hooks/use-accounts';
|
||||||
import type { Account } from '@/lib/api-client';
|
import type { Account } from '@/lib/api-client';
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
|||||||
const setDefaultMutation = useSetDefaultAccount();
|
const setDefaultMutation = useSetDefaultAccount();
|
||||||
const deleteMutation = useDeleteAccount();
|
const deleteMutation = useDeleteAccount();
|
||||||
const resetDefaultMutation = useResetDefaultAccount();
|
const resetDefaultMutation = useResetDefaultAccount();
|
||||||
|
const updateContextMutation = useUpdateAccountContext();
|
||||||
const [deleteTarget, setDeleteTarget] = useState<string | null>(null);
|
const [deleteTarget, setDeleteTarget] = useState<string | null>(null);
|
||||||
const [contextTarget, setContextTarget] = useState<Account | null>(null);
|
const [contextTarget, setContextTarget] = useState<Account | null>(null);
|
||||||
|
|
||||||
@@ -130,8 +132,14 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
|||||||
size: 220,
|
size: 220,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const isDefault = row.original.name === defaultAccount;
|
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 isCliproxy = row.original.type === 'cliproxy';
|
||||||
|
const isShared = row.original.context_mode === 'shared';
|
||||||
|
const hasLegacyInference =
|
||||||
|
row.original.context_inferred || row.original.continuity_inferred;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -147,6 +155,63 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
|||||||
<Pencil className="w-4 h-4" />
|
<Pencil className="w-4 h-4" />
|
||||||
</Button>
|
</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
|
<Button
|
||||||
variant={isDefault ? 'secondary' : 'default'}
|
variant={isDefault ? 'secondary' : 'default'}
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -205,7 +270,7 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
|||||||
created: 'w-[150px]',
|
created: 'w-[150px]',
|
||||||
last_used: 'w-[150px]',
|
last_used: 'w-[150px]',
|
||||||
context: 'w-[170px]',
|
context: 'w-[170px]',
|
||||||
actions: 'w-[220px]',
|
actions: 'w-[340px]',
|
||||||
}[header.id] || 'w-auto';
|
}[header.id] || 'w-auto';
|
||||||
|
|
||||||
return (
|
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 { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { AccountsTable } from '@/components/account/accounts-table';
|
||||||
import { CreateAuthProfileDialog } from '@/components/account/create-auth-profile-dialog';
|
import { CreateAuthProfileDialog } from '@/components/account/create-auth-profile-dialog';
|
||||||
import { CopyButton } from '@/components/ui/copy-button';
|
import { CopyButton } from '@/components/ui/copy-button';
|
||||||
@@ -13,8 +24,9 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
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 { 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 { cn } from '@/lib/utils';
|
||||||
import type { LucideIcon } from 'lucide-react';
|
import type { LucideIcon } from 'lucide-react';
|
||||||
|
|
||||||
@@ -90,7 +102,11 @@ function StrategyCard({
|
|||||||
export function AccountsPage() {
|
export function AccountsPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, isLoading } = useAccounts();
|
const { data, isLoading } = useAccounts();
|
||||||
|
const confirmLegacyMutation = useConfirmLegacyAccountPolicies();
|
||||||
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
||||||
|
const [showGuideRail, setShowGuideRail] = useState(true);
|
||||||
|
const [guideOpen, setGuideOpen] = useState(false);
|
||||||
|
|
||||||
const authAccounts = data?.accounts || [];
|
const authAccounts = data?.accounts || [];
|
||||||
const cliproxyCount = data?.cliproxyCount || 0;
|
const cliproxyCount = data?.cliproxyCount || 0;
|
||||||
const legacyContextCount = data?.legacyContextCount || 0;
|
const legacyContextCount = data?.legacyContextCount || 0;
|
||||||
@@ -99,7 +115,16 @@ export function AccountsPage() {
|
|||||||
const sharedStandardCount = data?.sharedStandardCount || 0;
|
const sharedStandardCount = data?.sharedStandardCount || 0;
|
||||||
const deeperSharedCount = data?.deeperSharedCount || 0;
|
const deeperSharedCount = data?.deeperSharedCount || 0;
|
||||||
const isolatedCount = data?.isolatedCount || 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -128,11 +153,20 @@ export function AccountsPage() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="justify-start"
|
className="justify-start"
|
||||||
onClick={() => navigate('/cliproxy')}
|
onClick={handleOpenClaudePool}
|
||||||
>
|
>
|
||||||
Open CLIProxy Claude Pool
|
Open CLIProxy Claude Pool
|
||||||
<ArrowRight className="w-4 h-4 ml-auto" />
|
<ArrowRight className="w-4 h-4 ml-auto" />
|
||||||
</Button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -176,7 +210,7 @@ export function AccountsPage() {
|
|||||||
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
||||||
Migration Follow-up
|
Migration Follow-up
|
||||||
</p>
|
</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">
|
<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" />
|
<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">
|
<div className="space-y-1 text-xs">
|
||||||
@@ -196,6 +230,18 @@ export function AccountsPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
@@ -222,19 +268,29 @@ export function AccountsPage() {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="outline">ccs auth Workspace</Badge>
|
<Badge variant="outline">ccs auth Workspace</Badge>
|
||||||
<Badge variant="secondary">History Sync Controls</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>
|
</div>
|
||||||
<h2 className="mt-2 text-xl font-semibold tracking-tight">Auth Accounts</h2>
|
<h2 className="mt-2 text-xl font-semibold tracking-tight">Auth Accounts</h2>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
This table is intentionally scoped to
|
This table is intentionally scoped to
|
||||||
<code className="mx-1 rounded bg-muted px-1 py-0.5">ccs auth</code>
|
<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
|
accounts. Use
|
||||||
continuity behavior.
|
<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>
|
</p>
|
||||||
</div>
|
</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 && (
|
{cliproxyCount > 0 && (
|
||||||
<Alert variant="info" className="mb-4">
|
<Alert variant="info">
|
||||||
<Zap className="h-4 w-4" />
|
<Zap className="h-4 w-4" />
|
||||||
<AlertTitle>CLIProxy pool accounts are managed in their own page</AlertTitle>
|
<AlertTitle>CLIProxy pool accounts are managed in their own page</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
@@ -246,15 +302,15 @@ export function AccountsPage() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Card className="h-full flex flex-col">
|
<Card className="flex flex-col">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="text-lg">Account Matrix</CardTitle>
|
<CardTitle className="text-lg">Account Matrix</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Shared total: {sharedCount}. Update sync behavior from the pencil action in each
|
Shared total: {sharedCount}. Actions now include quick link/unlink plus legacy
|
||||||
row.
|
confirmation.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex-1 min-h-0">
|
<CardContent>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="text-muted-foreground">Loading accounts...</div>
|
<div className="text-muted-foreground">Loading accounts...</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -265,64 +321,137 @@ export function AccountsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right guidance column */}
|
{/* Right action center */}
|
||||||
<div className="w-80 shrink-0 flex flex-col bg-muted/20">
|
{showGuideRail ? (
|
||||||
<div className="p-4 border-b bg-background">
|
<div className="w-80 shrink-0 flex flex-col bg-muted/20">
|
||||||
<h3 className="font-semibold">Continuity Guide</h3>
|
<div className="p-4 border-b bg-background flex items-start justify-between gap-3">
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<div>
|
||||||
Choose the lightest mode that solves your workflow.
|
<h3 className="font-semibold">Action Center</h3>
|
||||||
</p>
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
</div>
|
High-value actions for pool auth and legacy cleanup.
|
||||||
|
</p>
|
||||||
<ScrollArea className="flex-1">
|
</div>
|
||||||
<div className="p-4 space-y-3">
|
<Button variant="ghost" size="icon" onClick={() => setShowGuideRail(false)}>
|
||||||
<Card>
|
<ChevronLeft className="h-4 w-4" />
|
||||||
<CardHeader className="pb-2">
|
</Button>
|
||||||
<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>
|
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -342,10 +471,24 @@ export function AccountsPage() {
|
|||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
Create Account
|
Create Account
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" className="w-full" onClick={() => navigate('/cliproxy')}>
|
<Button variant="outline" className="w-full" onClick={handleOpenClaudePool}>
|
||||||
Open CLIProxy Claude Pool
|
Open CLIProxy Claude Pool
|
||||||
<ArrowRight className="w-4 h-4 ml-2" />
|
<ArrowRight className="w-4 h-4 ml-2" />
|
||||||
</Button>
|
</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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Right panel: Provider Editor with split-view (settings + code editor)
|
* 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 { useQueryClient } from '@tanstack/react-query';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
} from '@/hooks/use-cliproxy';
|
} from '@/hooks/use-cliproxy';
|
||||||
import type { AuthStatus, Variant } from '@/lib/api-client';
|
import type { AuthStatus, Variant } from '@/lib/api-client';
|
||||||
import { MODEL_CATALOGS } from '@/lib/model-catalogs';
|
import { MODEL_CATALOGS } from '@/lib/model-catalogs';
|
||||||
|
import { getProviderDisplayName, isValidProvider } from '@/lib/provider-config';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Sidebar provider item
|
// Sidebar provider item
|
||||||
@@ -198,9 +199,14 @@ export function CliproxyPage() {
|
|||||||
const deleteMutation = useDeleteVariant();
|
const deleteMutation = useDeleteVariant();
|
||||||
|
|
||||||
// Selection state: either a provider or a variant
|
// 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>(() => {
|
const [selectedProvider, setSelectedProviderState] = useState<string | null>(() => {
|
||||||
if (typeof window !== 'undefined') {
|
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 localStorage.getItem('cliproxy-selected-provider');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -211,7 +217,25 @@ export function CliproxyPage() {
|
|||||||
provider: string;
|
provider: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
isFirstAccount: boolean;
|
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 providers = useMemo(() => authData?.authStatus || [], [authData?.authStatus]);
|
||||||
const isRemoteMode = authData?.source === 'remote';
|
const isRemoteMode = authData?.source === 'remote';
|
||||||
|
|||||||
Reference in New Issue
Block a user