Files
roboco/panel/src/components/knowledge-base/kb-stats-card.tsx
T

155 lines
5.2 KiB
TypeScript
Raw Normal View History

2026-04-20 15:10:54 +02:00
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { HelpTip } from "@/components/ui/help-tip";
import { getIndexTypeDescription } from "./kb-index-type-badge";
2026-04-20 15:10:54 +02:00
import { KBStats, KBIndexType } from "@/types";
2026-06-29 05:38:21 +02:00
import {
Database,
FileText,
MessageSquare,
BookOpen,
AlertTriangle,
Scale,
GitBranch,
ClipboardCheck,
Lightbulb,
ScrollText,
StickyNote,
2026-06-29 05:38:21 +02:00
} from "lucide-react";
2026-04-20 15:10:54 +02:00
import { formatDistanceToNow } from "date-fns";
const indexIcons: Record<KBIndexType, React.ReactNode> = {
[KBIndexType.DOCUMENTATION]: <FileText className="h-4 w-4 text-blue-500" />,
2026-06-29 05:38:21 +02:00
[KBIndexType.CONVERSATIONS]: (
<MessageSquare className="h-4 w-4 text-green-500" />
),
2026-04-20 15:10:54 +02:00
[KBIndexType.JOURNALS]: <BookOpen className="h-4 w-4 text-orange-500" />,
[KBIndexType.ERRORS]: <AlertTriangle className="h-4 w-4 text-red-500" />,
[KBIndexType.STANDARDS]: <Scale className="h-4 w-4 text-cyan-500" />,
[KBIndexType.DECISIONS]: <GitBranch className="h-4 w-4 text-indigo-500" />,
[KBIndexType.REVIEWS]: <ClipboardCheck className="h-4 w-4 text-pink-500" />,
[KBIndexType.LEARNINGS]: <Lightbulb className="h-4 w-4 text-yellow-500" />,
[KBIndexType.PLAYBOOKS]: <ScrollText className="h-4 w-4 text-emerald-500" />,
[KBIndexType.VAULT_NOTES]: <StickyNote className="h-4 w-4 text-violet-500" />,
2026-04-20 15:10:54 +02:00
};
const indexLabels: Record<KBIndexType, string> = {
[KBIndexType.DOCUMENTATION]: "Docs",
[KBIndexType.CONVERSATIONS]: "Convos",
[KBIndexType.JOURNALS]: "Journals",
[KBIndexType.ERRORS]: "Errors",
[KBIndexType.STANDARDS]: "Standards",
[KBIndexType.DECISIONS]: "Decisions",
[KBIndexType.REVIEWS]: "Reviews",
[KBIndexType.LEARNINGS]: "Learnings",
[KBIndexType.PLAYBOOKS]: "Playbooks",
[KBIndexType.VAULT_NOTES]: "Vault Notes",
2026-04-20 15:10:54 +02:00
};
interface KBStatsCardProps {
stats: KBStats | undefined;
isLoading: boolean;
}
export function KBStatsCard({ stats, isLoading }: KBStatsCardProps) {
if (isLoading) {
return (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<Database className="h-4 w-4" />
Index Stats
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="flex items-center justify-between">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-4 w-10" />
</div>
))}
</CardContent>
</Card>
);
}
if (!stats || !Array.isArray(stats.indexes)) {
return (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<Database className="h-4 w-4" />
Index Stats
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">No data available</p>
</CardContent>
</Card>
);
}
const latestUpdated = stats.indexes.reduce<string | null>(
(acc, idx) =>
2026-06-29 05:38:21 +02:00
idx.last_updated && (!acc || idx.last_updated > acc)
? idx.last_updated
: acc,
null,
);
2026-04-20 15:10:54 +02:00
return (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<Database className="h-4 w-4" />
Index Stats
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{stats.indexes.map((idx) => (
2026-06-29 05:38:21 +02:00
<div
key={idx.index_type}
className="flex items-center justify-between text-sm"
>
<HelpTip label={getIndexTypeDescription(idx.index_type)}>
<div className="flex items-center gap-2 w-fit">
{indexIcons[idx.index_type]}
<span>{indexLabels[idx.index_type]}</span>
</div>
</HelpTip>
2026-04-20 15:10:54 +02:00
<div className="text-right">
2026-06-29 05:38:21 +02:00
<span className="font-medium">
{idx.document_count.toLocaleString()}
</span>
2026-04-20 15:10:54 +02:00
<span className="text-muted-foreground text-xs ml-1">docs</span>
</div>
</div>
))}
<div className="pt-2 border-t">
<div className="flex items-center justify-between text-sm">
<span className="font-medium">Total</span>
2026-06-29 05:38:21 +02:00
<span className="font-bold">
{stats.total_documents.toLocaleString()}
</span>
2026-04-20 15:10:54 +02:00
</div>
<div className="flex items-center justify-between text-xs text-muted-foreground mt-1">
<HelpTip label="A chunk is a segment of a document split for embedding — one document can produce many chunks">
<span className="w-fit">Chunks</span>
</HelpTip>
2026-04-20 15:10:54 +02:00
<span>{stats.total_chunks.toLocaleString()}</span>
</div>
</div>
{latestUpdated && (
<HelpTip label={new Date(latestUpdated).toLocaleString()}>
<p className="text-xs text-muted-foreground pt-1 w-fit">
Updated {formatDistanceToNow(new Date(latestUpdated))} ago
</p>
</HelpTip>
2026-04-20 15:10:54 +02:00
)}
</CardContent>
</Card>
);
}