feat(accounts-ui): add history sync learning visualization

This commit is contained in:
Tam Nhu Tran
2026-02-26 20:59:16 +07:00
parent a974efb8b1
commit 6cfc8d4a45
2 changed files with 182 additions and 18 deletions
@@ -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 -&gt; Shared</Badge>
<Badge variant="secondary">Unlink: Shared -&gt; 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
View File
@@ -19,6 +19,7 @@ import {
} from 'lucide-react';
import { AccountsTable } from '@/components/account/accounts-table';
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 { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
@@ -115,6 +116,13 @@ export function AccountsPage() {
const sharedStandardCount = data?.sharedStandardCount || 0;
const deeperSharedCount = data?.deeperSharedCount || 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(
(account) => account.context_inferred || account.continuity_inferred
@@ -149,25 +157,10 @@ export function AccountsPage() {
<Plus className="w-4 h-4 mr-2" />
Create Account
</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>
<p className="text-[11px] text-muted-foreground">
Pool auth actions live in Action Center to avoid duplicate controls.
</p>
</div>
<ScrollArea className="flex-1">
@@ -302,6 +295,14 @@ export function AccountsPage() {
</Alert>
)}
<HistorySyncLearningMap
isolatedCount={isolatedCount}
sharedStandardCount={sharedStandardCount}
deeperSharedCount={deeperSharedCount}
sharedGroups={sharedGroups}
legacyTargetCount={legacyTargetCount}
/>
<Card className="flex flex-col">
<CardHeader className="pb-3">
<CardTitle className="text-lg">Account Matrix</CardTitle>