Skip to main content
Glama

Karakeep MCP server

by karakeep-app
ImportSessionCard.tsx9.33 kB
"use client"; import Link from "next/link"; import ActionConfirmingDialog from "@/components/ui/action-confirming-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { useDeleteImportSession, useImportSessionStats, } from "@/lib/hooks/useImportSessions"; import { useTranslation } from "@/lib/i18n/client"; import { formatDistanceToNow } from "date-fns"; import { AlertCircle, CheckCircle2, ClipboardList, Clock, ExternalLink, Loader2, Trash2, } from "lucide-react"; import type { ZImportSessionWithStats } from "@karakeep/shared/types/importSessions"; interface ImportSessionCardProps { session: ZImportSessionWithStats; } function getStatusColor(status: string) { switch (status) { case "pending": return "bg-muted text-muted-foreground"; case "in_progress": return "bg-blue-500/10 text-blue-700 dark:text-blue-400"; case "completed": return "bg-green-500/10 text-green-700 dark:text-green-400"; case "failed": return "bg-destructive/10 text-destructive"; default: return "bg-muted text-muted-foreground"; } } function getStatusIcon(status: string) { switch (status) { case "pending": return <Clock className="h-4 w-4" />; case "in_progress": return <Loader2 className="h-4 w-4 animate-spin" />; case "completed": return <CheckCircle2 className="h-4 w-4" />; case "failed": return <AlertCircle className="h-4 w-4" />; default: return <Clock className="h-4 w-4" />; } } export function ImportSessionCard({ session }: ImportSessionCardProps) { const { t } = useTranslation(); const { data: liveStats } = useImportSessionStats(session.id); const deleteSession = useDeleteImportSession(); const statusLabels: Record<string, string> = { pending: t("settings.import_sessions.status.pending"), in_progress: t("settings.import_sessions.status.in_progress"), completed: t("settings.import_sessions.status.completed"), failed: t("settings.import_sessions.status.failed"), }; // Use live stats if available, otherwise fallback to session stats const stats = liveStats || session; const progress = stats.totalBookmarks > 0 ? ((stats.completedBookmarks + stats.failedBookmarks) / stats.totalBookmarks) * 100 : 0; const canDelete = stats.status !== "in_progress"; return ( <Card className="transition-all hover:shadow-md"> <CardHeader className="pb-3"> <div className="flex items-start justify-between"> <div className="flex-1"> <h3 className="font-medium">{session.name}</h3> <p className="mt-1 text-sm text-accent-foreground"> {t("settings.import_sessions.created_at", { time: formatDistanceToNow(session.createdAt, { addSuffix: true, }), })} </p> </div> <div className="flex items-center gap-2"> <Badge className={`${getStatusColor(stats.status)} hover:bg-inherit`} > {getStatusIcon(stats.status)} <span className="ml-1 capitalize"> {statusLabels[stats.status] ?? stats.status.replace("_", " ")} </span> </Badge> </div> </div> </CardHeader> <CardContent className="pt-0"> <div className="space-y-3"> {/* Progress Section */} <div className="space-y-3"> <div className="flex items-center justify-between"> <h4 className="text-sm font-medium text-muted-foreground"> {t("settings.import_sessions.progress")} </h4> <div className="flex items-center gap-2"> <span className="text-sm font-medium"> {stats.completedBookmarks + stats.failedBookmarks} /{" "} {stats.totalBookmarks} </span> <Badge variant="outline" className="text-xs"> {Math.round(progress)}% </Badge> </div> </div> {stats.totalBookmarks > 0 && ( <Progress value={progress} className="h-3" /> )} </div> {/* Stats Breakdown */} {stats.totalBookmarks > 0 && ( <div className="space-y-3"> <div className="flex flex-wrap gap-2"> {stats.pendingBookmarks > 0 && ( <Badge variant="secondary" className="bg-amber-500/10 text-amber-700 hover:bg-amber-500/10 dark:text-amber-400" > <Clock className="mr-1.5 h-3 w-3" /> {t("settings.import_sessions.badges.pending", { count: stats.pendingBookmarks, })} </Badge> )} {stats.processingBookmarks > 0 && ( <Badge variant="secondary" className="bg-blue-500/10 text-blue-700 hover:bg-blue-500/10 dark:text-blue-400" > <Loader2 className="mr-1.5 h-3 w-3 animate-spin" /> {t("settings.import_sessions.badges.processing", { count: stats.processingBookmarks, })} </Badge> )} {stats.completedBookmarks > 0 && ( <Badge variant="secondary" className="bg-green-500/10 text-green-700 hover:bg-green-500/10 dark:text-green-400" > <CheckCircle2 className="mr-1.5 h-3 w-3" /> {t("settings.import_sessions.badges.completed", { count: stats.completedBookmarks, })} </Badge> )} {stats.failedBookmarks > 0 && ( <Badge variant="secondary" className="bg-destructive/10 text-destructive hover:bg-destructive/10" > <AlertCircle className="mr-1.5 h-3 w-3" /> {t("settings.import_sessions.badges.failed", { count: stats.failedBookmarks, })} </Badge> )} </div> </div> )} {/* Root List Link */} {session.rootListId && ( <div className="rounded-lg border bg-muted/50 p-3 dark:bg-muted/20"> <div className="flex items-center gap-2 text-sm"> <ClipboardList className="h-4 w-4 text-muted-foreground" /> <span className="font-medium text-muted-foreground"> {t("settings.import_sessions.imported_to")} </span> <Link href={`/dashboard/lists/${session.rootListId}`} className="flex items-center gap-1 font-medium text-primary transition-colors hover:text-primary/80" target="_blank" > {t("settings.import_sessions.view_list")} <ExternalLink className="h-3 w-3" /> </Link> </div> </div> )} {/* Message */} {stats.message && ( <div className="rounded-lg border bg-muted/50 p-3 text-sm text-muted-foreground dark:bg-muted/20"> {stats.message} </div> )} {/* Actions */} <div className="flex items-center justify-end pt-2"> <div className="flex items-center gap-2"> {canDelete && ( <ActionConfirmingDialog title={t("settings.import_sessions.delete_dialog_title")} description={ <div> {t("settings.import_sessions.delete_dialog_description", { name: session.name, })} </div> } actionButton={(setDialogOpen) => ( <Button variant="destructive" onClick={() => { deleteSession.mutateAsync({ importSessionId: session.id, }); setDialogOpen(false); }} disabled={deleteSession.isPending} > {t("settings.import_sessions.delete_session")} </Button> )} > <Button variant="destructive" size="sm" disabled={deleteSession.isPending} > <Trash2 className="mr-1 h-4 w-4" /> {t("actions.delete")} </Button> </ActionConfirmingDialog> )} </div> </div> </div> </CardContent> </Card> ); }

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