From fe1ae8436df84048dda229061204a634ab5ab3ab Mon Sep 17 00:00:00 2001 From: ashim-hq Date: Sat, 18 Apr 2026 02:47:42 +0800 Subject: [PATCH] feat: add download badge to uninstalled AI tools in tool grid Show a download icon on AI tools that are not yet installed, and fetch feature status on mount in both the sidebar tool panel and fullscreen grid page. --- apps/web/src/components/common/tool-card.tsx | 9 ++++++++- apps/web/src/components/layout/tool-panel.tsx | 6 ++++++ apps/web/src/pages/fullscreen-grid-page.tsx | 6 ++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/common/tool-card.tsx b/apps/web/src/components/common/tool-card.tsx index 79b9a52d..32a817ab 100644 --- a/apps/web/src/components/common/tool-card.tsx +++ b/apps/web/src/components/common/tool-card.tsx @@ -1,8 +1,10 @@ import type { Tool } from "@ashim/shared"; -import { FileImage, Star } from "lucide-react"; +import { PYTHON_SIDECAR_TOOLS } from "@ashim/shared"; +import { Download, FileImage, Star } from "lucide-react"; import { Link } from "react-router-dom"; import { ICON_MAP } from "@/lib/icon-map"; import { cn } from "@/lib/utils"; +import { useFeaturesStore } from "@/stores/features-store"; interface ToolCardProps { tool: Tool; @@ -12,6 +14,10 @@ export function ToolCard({ tool }: ToolCardProps) { const IconComponent = (ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? FileImage; + const isAiTool = (PYTHON_SIDECAR_TOOLS as readonly string[]).includes(tool.id); + const isInstalled = useFeaturesStore((s) => s.isToolInstalled)(tool.id); + const showDownloadBadge = isAiTool && !isInstalled; + return (
); diff --git a/apps/web/src/components/layout/tool-panel.tsx b/apps/web/src/components/layout/tool-panel.tsx index a7641053..a00bf9d9 100644 --- a/apps/web/src/components/layout/tool-panel.tsx +++ b/apps/web/src/components/layout/tool-panel.tsx @@ -1,5 +1,6 @@ import { CATEGORIES, TOOLS } from "@ashim/shared"; import { useEffect, useMemo, useState } from "react"; +import { useFeaturesStore } from "@/stores/features-store"; import { useSettingsStore } from "@/stores/settings-store"; import { SearchBar } from "../common/search-bar"; import { ToolCard } from "../common/tool-card"; @@ -7,11 +8,16 @@ import { ToolCard } from "../common/tool-card"; export function ToolPanel() { const [search, setSearch] = useState(""); const { disabledTools, experimentalEnabled, loaded, fetch } = useSettingsStore(); + const fetchFeatures = useFeaturesStore((s) => s.fetch); useEffect(() => { fetch(); }, [fetch]); + useEffect(() => { + fetchFeatures(); + }, [fetchFeatures]); + const visibleTools = useMemo(() => { if (!loaded) return []; return TOOLS.filter((t) => { diff --git a/apps/web/src/pages/fullscreen-grid-page.tsx b/apps/web/src/pages/fullscreen-grid-page.tsx index dcca7ff7..d2cf96dc 100644 --- a/apps/web/src/pages/fullscreen-grid-page.tsx +++ b/apps/web/src/pages/fullscreen-grid-page.tsx @@ -7,6 +7,7 @@ import { GemLogo } from "@/components/common/gem-logo"; import { apiGet } from "@/lib/api"; import { ICON_MAP } from "@/lib/icon-map"; import { cn } from "@/lib/utils"; +import { useFeaturesStore } from "@/stores/features-store"; export function FullscreenGridPage() { const [search, setSearch] = useState(""); @@ -14,6 +15,11 @@ export function FullscreenGridPage() { const navigate = useNavigate(); const [disabledTools, setDisabledTools] = useState([]); const [experimentalEnabled, setExperimentalEnabled] = useState(false); + const fetchFeatures = useFeaturesStore((s) => s.fetch); + + useEffect(() => { + fetchFeatures(); + }, [fetchFeatures]); useEffect(() => { apiGet<{ settings: Record }>("/v1/settings")