mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-30 14:23:37 +00:00
feat(accounts-ui): add history sync learning visualization
This commit is contained in:
@@ -0,0 +1,163 @@
|
|||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
ArrowRightLeft,
|
||||||
|
Layers3,
|
||||||
|
Link2,
|
||||||
|
Unlink,
|
||||||
|
Waves,
|
||||||
|
type LucideIcon,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
interface HistorySyncLearningMapProps {
|
||||||
|
isolatedCount: number;
|
||||||
|
sharedStandardCount: number;
|
||||||
|
deeperSharedCount: number;
|
||||||
|
sharedGroups: string[];
|
||||||
|
legacyTargetCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type StageTone = 'isolated' | 'shared' | 'deeper';
|
||||||
|
|
||||||
|
function StageCard({
|
||||||
|
title,
|
||||||
|
count,
|
||||||
|
icon: Icon,
|
||||||
|
tone,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
count: number;
|
||||||
|
icon: LucideIcon;
|
||||||
|
tone: StageTone;
|
||||||
|
description: string;
|
||||||
|
}) {
|
||||||
|
const toneClasses: Record<StageTone, { card: string; icon: string; count: string }> = {
|
||||||
|
isolated: {
|
||||||
|
card: 'border-blue-300/60 bg-blue-50/40 dark:border-blue-900/40 dark:bg-blue-900/10',
|
||||||
|
icon: 'text-blue-700 dark:text-blue-400',
|
||||||
|
count: 'text-blue-700 dark:text-blue-400',
|
||||||
|
},
|
||||||
|
shared: {
|
||||||
|
card: 'border-emerald-300/60 bg-emerald-50/40 dark:border-emerald-900/40 dark:bg-emerald-900/10',
|
||||||
|
icon: 'text-emerald-700 dark:text-emerald-400',
|
||||||
|
count: 'text-emerald-700 dark:text-emerald-400',
|
||||||
|
},
|
||||||
|
deeper: {
|
||||||
|
card: 'border-indigo-300/60 bg-indigo-50/40 dark:border-indigo-900/40 dark:bg-indigo-900/10',
|
||||||
|
icon: 'text-indigo-700 dark:text-indigo-400',
|
||||||
|
count: 'text-indigo-700 dark:text-indigo-400',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('rounded-md border p-3', toneClasses[tone].card)}>
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<p className="text-sm font-semibold">{title}</p>
|
||||||
|
<Icon className={cn('h-4 w-4', toneClasses[tone].icon)} />
|
||||||
|
</div>
|
||||||
|
<p className={cn('mt-1 text-2xl font-mono font-semibold', toneClasses[tone].count)}>
|
||||||
|
{count}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-xs text-muted-foreground">{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HistorySyncLearningMap({
|
||||||
|
isolatedCount,
|
||||||
|
sharedStandardCount,
|
||||||
|
deeperSharedCount,
|
||||||
|
sharedGroups,
|
||||||
|
legacyTargetCount,
|
||||||
|
}: HistorySyncLearningMapProps) {
|
||||||
|
const groupsToShow = sharedGroups.length > 0 ? sharedGroups : ['default'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="border-dashed">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<CardTitle className="text-base">How History Sync Works</CardTitle>
|
||||||
|
<Badge variant="outline">Learning Map</Badge>
|
||||||
|
</div>
|
||||||
|
<CardDescription>
|
||||||
|
Accounts can move between modes at any time. Link/unlink is instant; Edit gives full
|
||||||
|
control of group and continuity depth.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid gap-3 lg:grid-cols-[1fr_auto_1fr_auto_1fr] lg:items-center">
|
||||||
|
<StageCard
|
||||||
|
title="Isolated"
|
||||||
|
count={isolatedCount}
|
||||||
|
icon={Unlink}
|
||||||
|
tone="isolated"
|
||||||
|
description="No shared history with other accounts."
|
||||||
|
/>
|
||||||
|
<div className="hidden lg:flex justify-center text-muted-foreground">
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<StageCard
|
||||||
|
title="Shared (Standard)"
|
||||||
|
count={sharedStandardCount}
|
||||||
|
icon={Link2}
|
||||||
|
tone="shared"
|
||||||
|
description="Shared project context only."
|
||||||
|
/>
|
||||||
|
<div className="hidden lg:flex justify-center text-muted-foreground">
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<StageCard
|
||||||
|
title="Shared (Deeper)"
|
||||||
|
count={deeperSharedCount}
|
||||||
|
icon={Waves}
|
||||||
|
tone="deeper"
|
||||||
|
description="Adds continuity data: session-env/file-history/todos/shell-snapshots."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 lg:grid-cols-2">
|
||||||
|
<div className="rounded-md border bg-muted/20 p-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ArrowRightLeft className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<p className="text-sm font-semibold">Mode Switch Actions</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
|
<Badge variant="secondary">Link: Isolated -> Shared</Badge>
|
||||||
|
<Badge variant="secondary">Unlink: Shared -> Isolated</Badge>
|
||||||
|
<Badge variant="secondary">Edit: Group + Deeper</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-md border bg-muted/20 p-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Layers3 className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<p className="text-sm font-semibold">History Sync Group</p>
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
|
Accounts in the same group share the same project context bucket. Group names are
|
||||||
|
user-defined lanes, with <code>default</code> as fallback.
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
|
{groupsToShow.map((group) => (
|
||||||
|
<Badge key={group} variant="outline" className="font-mono text-[11px]">
|
||||||
|
{group}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{legacyTargetCount > 0 && (
|
||||||
|
<div className="rounded-md border border-amber-500/50 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-300">
|
||||||
|
{legacyTargetCount} legacy account
|
||||||
|
{legacyTargetCount > 1 ? 's still need' : ' still needs'} explicit confirmation. Use{' '}
|
||||||
|
<strong>Confirm Legacy Policies</strong> in Action Center.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
+19
-18
@@ -19,6 +19,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} 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 { HistorySyncLearningMap } from '@/components/account/history-sync-learning-map';
|
||||||
import { CopyButton } from '@/components/ui/copy-button';
|
import { CopyButton } from '@/components/ui/copy-button';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
@@ -115,6 +116,13 @@ 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 sharedGroups = Array.from(
|
||||||
|
new Set(
|
||||||
|
authAccounts
|
||||||
|
.filter((account) => account.context_mode === 'shared')
|
||||||
|
.map((account) => account.context_group || 'default')
|
||||||
|
)
|
||||||
|
).sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
const legacyTargets = authAccounts.filter(
|
const legacyTargets = authAccounts.filter(
|
||||||
(account) => account.context_inferred || account.continuity_inferred
|
(account) => account.context_inferred || account.continuity_inferred
|
||||||
@@ -149,25 +157,10 @@ 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"
|
|
||||||
size="sm"
|
|
||||||
className="justify-start"
|
|
||||||
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>
|
||||||
|
<p className="text-[11px] text-muted-foreground">
|
||||||
|
Pool auth actions live in Action Center to avoid duplicate controls.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
@@ -302,6 +295,14 @@ export function AccountsPage() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<HistorySyncLearningMap
|
||||||
|
isolatedCount={isolatedCount}
|
||||||
|
sharedStandardCount={sharedStandardCount}
|
||||||
|
deeperSharedCount={deeperSharedCount}
|
||||||
|
sharedGroups={sharedGroups}
|
||||||
|
legacyTargetCount={legacyTargetCount}
|
||||||
|
/>
|
||||||
|
|
||||||
<Card className="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>
|
||||||
|
|||||||
Reference in New Issue
Block a user