2026-04-14 20:55:42 +08:00
|
|
|
import type { Tool } from "@ashim/shared";
|
2026-04-18 11:34:22 +08:00
|
|
|
import { PYTHON_SIDECAR_TOOLS, TOOL_BUNDLE_MAP } from "@ashim/shared";
|
2026-04-18 02:47:42 +08:00
|
|
|
import { Download, FileImage, Star } from "lucide-react";
|
2026-04-18 11:34:22 +08:00
|
|
|
import { useMemo } from "react";
|
2026-03-25 09:27:12 +08:00
|
|
|
import { Link } from "react-router-dom";
|
2026-04-15 18:13:23 +08:00
|
|
|
import { ICON_MAP } from "@/lib/icon-map";
|
2026-03-22 03:01:23 +08:00
|
|
|
import { cn } from "@/lib/utils";
|
2026-04-18 02:47:42 +08:00
|
|
|
import { useFeaturesStore } from "@/stores/features-store";
|
2026-03-22 03:01:23 +08:00
|
|
|
|
|
|
|
|
interface ToolCardProps {
|
|
|
|
|
tool: Tool;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 17:38:54 +08:00
|
|
|
export function ToolCard({ tool }: ToolCardProps) {
|
2026-04-15 18:13:23 +08:00
|
|
|
const IconComponent =
|
|
|
|
|
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? FileImage;
|
2026-03-22 03:01:23 +08:00
|
|
|
|
2026-04-18 02:47:42 +08:00
|
|
|
const isAiTool = (PYTHON_SIDECAR_TOOLS as readonly string[]).includes(tool.id);
|
2026-04-18 11:34:22 +08:00
|
|
|
const bundles = useFeaturesStore((s) => s.bundles);
|
|
|
|
|
const isInstalled = useMemo(() => {
|
|
|
|
|
if (!isAiTool) return true;
|
|
|
|
|
const bundleId = TOOL_BUNDLE_MAP[tool.id];
|
|
|
|
|
if (!bundleId) return true;
|
|
|
|
|
const bundle = bundles.find((b) => b.id === bundleId);
|
|
|
|
|
return bundle?.status === "installed";
|
|
|
|
|
}, [isAiTool, tool.id, bundles]);
|
2026-04-18 02:47:42 +08:00
|
|
|
const showDownloadBadge = isAiTool && !isInstalled;
|
|
|
|
|
|
2026-03-22 03:01:23 +08:00
|
|
|
return (
|
|
|
|
|
<div className="group flex items-center gap-3 relative">
|
|
|
|
|
<button
|
2026-03-26 01:10:13 +08:00
|
|
|
type="button"
|
2026-03-22 03:01:23 +08:00
|
|
|
className="opacity-0 group-hover:opacity-100 transition-opacity absolute -left-5"
|
|
|
|
|
title="Add to favourites"
|
|
|
|
|
>
|
|
|
|
|
<Star className="h-3 w-3 text-muted-foreground hover:text-yellow-500" />
|
|
|
|
|
</button>
|
|
|
|
|
<Link
|
|
|
|
|
to={tool.route}
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-3 py-2 px-3 rounded-lg w-full transition-colors",
|
|
|
|
|
"hover:bg-muted",
|
2026-03-25 09:27:12 +08:00
|
|
|
tool.disabled && "opacity-50 pointer-events-none",
|
2026-03-22 03:01:23 +08:00
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<IconComponent className="h-5 w-5 text-muted-foreground" />
|
|
|
|
|
<span className="text-sm font-medium text-foreground">{tool.name}</span>
|
2026-03-25 09:27:12 +08:00
|
|
|
{tool.experimental && (
|
2026-03-22 03:01:23 +08:00
|
|
|
<span className="text-[10px] px-1.5 py-0.5 rounded bg-orange-100 text-orange-600 font-medium">
|
2026-03-25 09:27:12 +08:00
|
|
|
Experimental
|
2026-03-22 03:01:23 +08:00
|
|
|
</span>
|
|
|
|
|
)}
|
2026-04-18 02:47:42 +08:00
|
|
|
{showDownloadBadge && <Download className="h-3.5 w-3.5 text-muted-foreground" />}
|
2026-03-22 03:01:23 +08:00
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|