Spaces:
Runtime error
Runtime error
| "use client"; | |
| import Link from "next/link"; | |
| import Image from "next/image"; | |
| import { useTranslations } from "next-intl"; | |
| import type { CliCatalogEntry } from "@/shared/schemas/cliCatalog"; | |
| import type { ToolBatchStatus } from "@/shared/types/cliBatchStatus"; | |
| import CliStatusBadge from "@/app/(dashboard)/dashboard/cli-code/components/CliStatusBadge"; | |
| import { cn } from "@/shared/utils/cn"; | |
| export interface CliToolCardProps { | |
| tool: CliCatalogEntry; | |
| batchStatus: ToolBatchStatus | null; | |
| detailHref: string; | |
| hasActiveProviders: boolean; | |
| } | |
| export default function CliToolCard({ | |
| tool, | |
| batchStatus, | |
| detailHref, | |
| hasActiveProviders, | |
| }: CliToolCardProps) { | |
| const t = useTranslations("cliCommon"); | |
| const installed = batchStatus?.detection.installed ?? false; | |
| const configStatus = batchStatus?.config.status ?? null; | |
| const version = batchStatus?.detection.version ?? "not found"; | |
| const endpoint = batchStatus?.config.endpoint ?? null; | |
| const showInstallChips = !installed && tool.configType !== "guide"; | |
| const title = ( | |
| <div className="flex items-center gap-2.5"> | |
| {/* Icon / image */} | |
| {tool.image ? ( | |
| <Image | |
| src={tool.image} | |
| alt={tool.name} | |
| width={32} | |
| height={32} | |
| className="rounded-md object-contain flex-shrink-0" | |
| /> | |
| ) : ( | |
| <span | |
| className="material-symbols-outlined text-[20px] flex-shrink-0" | |
| style={{ color: tool.color }} | |
| aria-hidden="true" | |
| > | |
| {tool.icon ?? "terminal"} | |
| </span> | |
| )} | |
| <div className="min-w-0 flex-1"> | |
| <div className="flex items-center gap-2 flex-wrap"> | |
| <span className="font-semibold text-text-main text-sm leading-tight truncate"> | |
| {tool.name} | |
| </span> | |
| <span className="text-[11px] text-text-muted font-mono bg-black/5 dark:bg-white/5 px-1.5 py-0.5 rounded"> | |
| {version} | |
| </span> | |
| </div> | |
| <p className="text-xs text-text-muted line-clamp-1 mt-0.5">{tool.description}</p> | |
| </div> | |
| <span className="material-symbols-outlined text-[18px] text-text-muted flex-shrink-0"> | |
| chevron_right | |
| </span> | |
| </div> | |
| ); | |
| return ( | |
| <Link | |
| href={detailHref} | |
| className={cn( | |
| "block min-h-[180px]", | |
| "bg-surface border border-black/5 dark:border-white/5 rounded-lg shadow-sm", | |
| "hover:shadow-md hover:border-primary/30 transition-all", | |
| "p-4 flex flex-col gap-3" | |
| )} | |
| > | |
| {/* Header */} | |
| {title} | |
| {/* Status strip */} | |
| <div className="flex items-center gap-2 flex-wrap"> | |
| {/* Detection */} | |
| <span | |
| className={cn( | |
| "inline-flex items-center gap-1 text-[11px] font-medium px-1.5 py-0.5 rounded", | |
| installed ? "text-green-600 dark:text-green-400" : "text-zinc-500 dark:text-zinc-400" | |
| )} | |
| > | |
| <span aria-hidden="true">{installed ? "✓" : "✗"}</span> | |
| {installed ? t("card.detected") : t("card.notDetected")} | |
| </span> | |
| {/* Config status */} | |
| {configStatus && ( | |
| <CliStatusBadge | |
| effectiveConfigStatus={configStatus} | |
| batchStatus={null} | |
| lastConfiguredAt={batchStatus?.config.lastConfiguredAt ?? null} | |
| /> | |
| )} | |
| {/* Endpoint */} | |
| {endpoint && ( | |
| <span className="text-[10px] text-text-muted font-mono truncate max-w-[140px]"> | |
| {endpoint} | |
| </span> | |
| )} | |
| </div> | |
| {/* Badges row */} | |
| <div className="flex items-center gap-1.5 flex-wrap"> | |
| {tool.baseUrlSupport === "partial" && ( | |
| <span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400"> | |
| <span aria-hidden="true">⚠</span> {t("card.baseUrlPartial")} | |
| </span> | |
| )} | |
| {tool.acpSpawnable === true && ( | |
| <span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-blue-500/10 text-blue-600 dark:text-blue-400"> | |
| {t("card.alsoAcp")} | |
| </span> | |
| )} | |
| {showInstallChips && ( | |
| <> | |
| <span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted"> | |
| 📋 Manual config | |
| </span> | |
| <span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted"> | |
| ⬇ Install | |
| </span> | |
| </> | |
| )} | |
| </div> | |
| {/* Footer */} | |
| <div className="mt-auto pt-1 flex items-center justify-between"> | |
| <span className="text-xs text-primary font-medium"> | |
| {installed ? t("card.configure") : t("card.howToInstall")} | |
| </span> | |
| {!hasActiveProviders && ( | |
| <span | |
| className="text-[10px] text-text-muted italic" | |
| title={t("card.connectProviderHint")} | |
| > | |
| {t("card.connectProviderHint")} | |
| </span> | |
| )} | |
| </div> | |
| </Link> | |
| ); | |
| } | |