Skip to main content
Glama

Karakeep MCP server

by karakeep-app
page.tsx18.4 kB
"use client"; import { useMemo } from "react"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { Skeleton } from "@/components/ui/skeleton"; import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; import { Archive, BarChart3, BookOpen, Clock, Database, FileText, Globe, Hash, Heart, Highlighter, Image, Link, List, TrendingUp, } from "lucide-react"; function formatBytes(bytes: number): string { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } function formatNumber(num: number): string { if (num >= 1000000) { return (num / 1000000).toFixed(1) + "M"; } if (num >= 1000) { return (num / 1000).toFixed(1) + "K"; } return num.toString(); } const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const hourLabels = Array.from({ length: 24 }, (_, i) => i === 0 ? "12 AM" : i < 12 ? `${i} AM` : i === 12 ? "12 PM" : `${i - 12} PM`, ); function SimpleBarChart({ data, maxValue, labels, }: { data: number[]; maxValue: number; labels: string[]; }) { return ( <div className="space-y-2"> {data.map((value, index) => ( <div key={index} className="flex items-center gap-3"> <div className="w-12 text-right text-xs text-muted-foreground"> {labels[index]} </div> <div className="relative h-2 flex-1 overflow-hidden rounded-full bg-muted"> <div className="h-full rounded-full bg-primary transition-all duration-300" style={{ width: `${maxValue > 0 ? (value / maxValue) * 100 : 0}%`, }} /> </div> <div className="w-8 text-right text-xs text-muted-foreground"> {value} </div> </div> ))} </div> ); } function StatCard({ title, value, icon, description, }: { title: string; value: string | number; icon: React.ReactNode; description?: string; }) { return ( <Card> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium">{title}</CardTitle> {icon} </CardHeader> <CardContent> <div className="text-2xl font-bold">{value}</div> {description && ( <p className="text-xs text-muted-foreground">{description}</p> )} </CardContent> </Card> ); } export default function StatsPage() { const { t } = useTranslation(); const { data: stats, isLoading } = api.users.stats.useQuery(); const { data: userSettings } = api.users.settings.useQuery(); const maxHourlyActivity = useMemo(() => { if (!stats) return 0; return Math.max( ...stats.bookmarkingActivity.byHour.map( (h: { hour: number; count: number }) => h.count, ), ); }, [stats]); const maxDailyActivity = useMemo(() => { if (!stats) return 0; return Math.max( ...stats.bookmarkingActivity.byDayOfWeek.map( (d: { day: number; count: number }) => d.count, ), ); }, [stats]); if (isLoading) { return ( <div className="space-y-6"> <div> <h1 className="text-3xl font-bold"> {t("settings.stats.usage_statistics")} </h1> <p className="text-muted-foreground"> {t("settings.stats.insights_description")} </p> </div> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> {Array.from({ length: 8 }).map((_, i) => ( <Card key={i}> <CardHeader className="space-y-0 pb-2"> <Skeleton className="h-4 w-24" /> </CardHeader> <CardContent> <Skeleton className="mb-2 h-8 w-16" /> <Skeleton className="h-3 w-32" /> </CardContent> </Card> ))} </div> </div> ); } if (!stats) { return ( <div className="flex h-64 items-center justify-center"> <p className="text-muted-foreground"> {t("settings.stats.failed_to_load")} </p> </div> ); } return ( <div className="space-y-6"> <div> <h1 className="text-3xl font-bold"> {t("settings.stats.usage_statistics")} </h1> <p className="text-muted-foreground"> Insights into your bookmarking habits and collection {userSettings?.timezone && userSettings.timezone !== "UTC" && ( <span className="block text-sm"> Times shown in {userSettings.timezone} timezone </span> )} </p> </div> {/* Overview Stats */} <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> <StatCard title={t("settings.stats.overview.total_bookmarks")} value={formatNumber(stats.numBookmarks)} icon={<BookOpen className="h-4 w-4 text-muted-foreground" />} description={t("settings.stats.overview.all_saved_items")} /> <StatCard title={t("settings.stats.overview.favorites")} value={formatNumber(stats.numFavorites)} icon={<Heart className="h-4 w-4 text-muted-foreground" />} description={t("settings.stats.overview.starred_bookmarks")} /> <StatCard title={t("settings.stats.overview.archived")} value={formatNumber(stats.numArchived)} icon={<Archive className="h-4 w-4 text-muted-foreground" />} description={t("settings.stats.overview.archived_items")} /> <StatCard title={t("settings.stats.overview.tags")} value={formatNumber(stats.numTags)} icon={<Hash className="h-4 w-4 text-muted-foreground" />} description={t("settings.stats.overview.unique_tags_created")} /> <StatCard title={t("settings.stats.overview.lists")} value={formatNumber(stats.numLists)} icon={<List className="h-4 w-4 text-muted-foreground" />} description={t("settings.stats.overview.bookmark_collections")} /> <StatCard title={t("settings.stats.overview.highlights")} value={formatNumber(stats.numHighlights)} icon={<Highlighter className="h-4 w-4 text-muted-foreground" />} description={t("settings.stats.overview.text_highlights")} /> <StatCard title={t("settings.stats.overview.storage_used")} value={formatBytes(stats.totalAssetSize)} icon={<Database className="h-4 w-4 text-muted-foreground" />} description={t("settings.stats.overview.total_asset_storage")} /> <StatCard title={t("settings.stats.overview.this_month")} value={formatNumber(stats.bookmarkingActivity.thisMonth)} icon={<TrendingUp className="h-4 w-4 text-muted-foreground" />} description={t("settings.stats.overview.bookmarks_added")} /> </div> <div className="grid gap-6 md:grid-cols-2"> {/* Bookmark Types */} <Card> <CardHeader> <CardTitle className="flex items-center gap-2"> <BarChart3 className="h-5 w-5" /> {t("settings.stats.bookmark_types.title")} </CardTitle> </CardHeader> <CardContent className="space-y-4"> <div className="space-y-3"> <div className="flex items-center justify-between"> <div className="flex items-center gap-2"> <Link className="h-4 w-4 text-blue-500" /> <span className="text-sm"> {t("settings.stats.bookmark_types.links")} </span> </div> <span className="text-sm font-medium"> {stats.bookmarksByType.link} </span> </div> <Progress value={ stats.numBookmarks > 0 ? (stats.bookmarksByType.link / stats.numBookmarks) * 100 : 0 } className="h-2" /> </div> <div className="space-y-3"> <div className="flex items-center justify-between"> <div className="flex items-center gap-2"> <FileText className="h-4 w-4 text-green-500" /> <span className="text-sm"> {t("settings.stats.bookmark_types.text_notes")} </span> </div> <span className="text-sm font-medium"> {stats.bookmarksByType.text} </span> </div> <Progress value={ stats.numBookmarks > 0 ? (stats.bookmarksByType.text / stats.numBookmarks) * 100 : 0 } className="h-2" /> </div> <div className="space-y-3"> <div className="flex items-center justify-between"> <div className="flex items-center gap-2"> <Image className="h-4 w-4 text-purple-500" /> <span className="text-sm"> {t("settings.stats.bookmark_types.assets")} </span> </div> <span className="text-sm font-medium"> {stats.bookmarksByType.asset} </span> </div> <Progress value={ stats.numBookmarks > 0 ? (stats.bookmarksByType.asset / stats.numBookmarks) * 100 : 0 } className="h-2" /> </div> </CardContent> </Card> {/* Recent Activity */} <Card className="flex flex-col"> <CardHeader> <CardTitle className="flex items-center gap-2"> <Clock className="h-5 w-5" /> {t("settings.stats.recent_activity.title")} </CardTitle> </CardHeader> <CardContent className="flex flex-1 items-center"> <div className="grid w-full grid-cols-3 gap-4 text-center"> <div> <div className="text-2xl font-bold text-green-600"> {stats.bookmarkingActivity.thisWeek} </div> <div className="text-xs text-muted-foreground"> {t("settings.stats.recent_activity.this_week")} </div> </div> <div> <div className="text-2xl font-bold text-blue-600"> {stats.bookmarkingActivity.thisMonth} </div> <div className="text-xs text-muted-foreground"> {t("settings.stats.recent_activity.this_month")} </div> </div> <div> <div className="text-2xl font-bold text-purple-600"> {stats.bookmarkingActivity.thisYear} </div> <div className="text-xs text-muted-foreground"> {t("settings.stats.recent_activity.this_year")} </div> </div> </div> </CardContent> </Card> {/* Top Domains */} <Card> <CardHeader> <CardTitle className="flex items-center gap-2"> <Globe className="h-5 w-5" /> {t("settings.stats.top_domains.title")} </CardTitle> </CardHeader> <CardContent> {stats.topDomains.length > 0 ? ( <div className="space-y-3"> {stats.topDomains .slice(0, 8) .map( ( domain: { domain: string; count: number }, index: number, ) => ( <div key={domain.domain} className="flex items-center justify-between" > <div className="flex items-center gap-2"> <div className="flex h-6 w-6 items-center justify-center rounded-full bg-muted text-xs font-medium"> {index + 1} </div> <span className="max-w-[200px] truncate text-sm" title={domain.domain} > {domain.domain} </span> </div> <Badge variant="secondary">{domain.count}</Badge> </div> ), )} </div> ) : ( <p className="text-sm text-muted-foreground"> {t("settings.stats.top_domains.no_domains_found")} </p> )} </CardContent> </Card> {/* Top Tags */} <Card> <CardHeader> <CardTitle className="flex items-center gap-2"> <Hash className="h-5 w-5" /> {t("settings.stats.most_used_tags.title")} </CardTitle> </CardHeader> <CardContent> {stats.tagUsage.length > 0 ? ( <div className="space-y-3"> {stats.tagUsage .slice(0, 8) .map( (tag: { name: string; count: number }, index: number) => ( <div key={tag.name} className="flex items-center justify-between" > <div className="flex items-center gap-2"> <div className="flex h-6 w-6 items-center justify-center rounded-full bg-muted text-xs font-medium"> {index + 1} </div> <span className="max-w-[200px] truncate text-sm" title={tag.name} > {tag.name} </span> </div> <Badge variant="secondary">{tag.count}</Badge> </div> ), )} </div> ) : ( <p className="text-sm text-muted-foreground"> {t("settings.stats.most_used_tags.no_tags_found")} </p> )} </CardContent> </Card> </div> {/* Activity Patterns */} <div className="grid gap-6 md:grid-cols-2"> {/* Hourly Activity */} <Card> <CardHeader> <CardTitle className="flex items-center gap-2"> <Clock className="h-5 w-5" /> {t("settings.stats.activity_patterns.activity_by_hour")} {userSettings?.timezone && userSettings.timezone !== "UTC" && ( <span className="text-xs font-normal text-muted-foreground"> ({userSettings.timezone}) </span> )} </CardTitle> </CardHeader> <CardContent> <SimpleBarChart data={stats.bookmarkingActivity.byHour.map( (h: { hour: number; count: number }) => h.count, )} maxValue={maxHourlyActivity} labels={hourLabels} /> </CardContent> </Card> {/* Daily Activity */} <Card> <CardHeader> <CardTitle className="flex items-center gap-2"> <BarChart3 className="h-5 w-5" /> {t("settings.stats.activity_patterns.activity_by_day")} {userSettings?.timezone && userSettings.timezone !== "UTC" && ( <span className="text-xs font-normal text-muted-foreground"> ({userSettings.timezone}) </span> )} </CardTitle> </CardHeader> <CardContent> <SimpleBarChart data={stats.bookmarkingActivity.byDayOfWeek.map( (d: { day: number; count: number }) => d.count, )} maxValue={maxDailyActivity} labels={dayNames} /> </CardContent> </Card> </div> {/* Asset Storage */} {stats.assetsByType.length > 0 && ( <Card> <CardHeader> <CardTitle className="flex items-center gap-2"> <Database className="h-5 w-5" /> {t("settings.stats.storage_breakdown.title")} </CardTitle> </CardHeader> <CardContent> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {stats.assetsByType.map( (asset: { type: string; count: number; totalSize: number }) => ( <div key={asset.type} className="space-y-2"> <div className="flex items-center justify-between"> <span className="text-sm font-medium capitalize"> {asset.type.replace(/([A-Z])/g, " $1").trim()} </span> <Badge variant="outline">{asset.count}</Badge> </div> <div className="text-xs text-muted-foreground"> {formatBytes(asset.totalSize)} </div> <Progress value={ stats.totalAssetSize > 0 ? (asset.totalSize / stats.totalAssetSize) * 100 : 0 } className="h-2" /> </div> ), )} </div> </CardContent> </Card> )} </div> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/karakeep-app/karakeep'

If you have feedback or need assistance with the MCP directory API, please join our Discord server