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.
This commit is contained in:
ashim-hq
2026-04-18 02:47:42 +08:00
parent 0b9c760e5e
commit fe1ae8436d
3 changed files with 20 additions and 1 deletions
+8 -1
View File
@@ -1,8 +1,10 @@
import type { Tool } from "@ashim/shared"; 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 { Link } from "react-router-dom";
import { ICON_MAP } from "@/lib/icon-map"; import { ICON_MAP } from "@/lib/icon-map";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useFeaturesStore } from "@/stores/features-store";
interface ToolCardProps { interface ToolCardProps {
tool: Tool; tool: Tool;
@@ -12,6 +14,10 @@ export function ToolCard({ tool }: ToolCardProps) {
const IconComponent = const IconComponent =
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? FileImage; (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 ( return (
<div className="group flex items-center gap-3 relative"> <div className="group flex items-center gap-3 relative">
<button <button
@@ -36,6 +42,7 @@ export function ToolCard({ tool }: ToolCardProps) {
Experimental Experimental
</span> </span>
)} )}
{showDownloadBadge && <Download className="h-3.5 w-3.5 text-muted-foreground" />}
</Link> </Link>
</div> </div>
); );
@@ -1,5 +1,6 @@
import { CATEGORIES, TOOLS } from "@ashim/shared"; import { CATEGORIES, TOOLS } from "@ashim/shared";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useFeaturesStore } from "@/stores/features-store";
import { useSettingsStore } from "@/stores/settings-store"; import { useSettingsStore } from "@/stores/settings-store";
import { SearchBar } from "../common/search-bar"; import { SearchBar } from "../common/search-bar";
import { ToolCard } from "../common/tool-card"; import { ToolCard } from "../common/tool-card";
@@ -7,11 +8,16 @@ import { ToolCard } from "../common/tool-card";
export function ToolPanel() { export function ToolPanel() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const { disabledTools, experimentalEnabled, loaded, fetch } = useSettingsStore(); const { disabledTools, experimentalEnabled, loaded, fetch } = useSettingsStore();
const fetchFeatures = useFeaturesStore((s) => s.fetch);
useEffect(() => { useEffect(() => {
fetch(); fetch();
}, [fetch]); }, [fetch]);
useEffect(() => {
fetchFeatures();
}, [fetchFeatures]);
const visibleTools = useMemo(() => { const visibleTools = useMemo(() => {
if (!loaded) return []; if (!loaded) return [];
return TOOLS.filter((t) => { return TOOLS.filter((t) => {
@@ -7,6 +7,7 @@ import { GemLogo } from "@/components/common/gem-logo";
import { apiGet } from "@/lib/api"; import { apiGet } from "@/lib/api";
import { ICON_MAP } from "@/lib/icon-map"; import { ICON_MAP } from "@/lib/icon-map";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useFeaturesStore } from "@/stores/features-store";
export function FullscreenGridPage() { export function FullscreenGridPage() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@@ -14,6 +15,11 @@ export function FullscreenGridPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const [disabledTools, setDisabledTools] = useState<string[]>([]); const [disabledTools, setDisabledTools] = useState<string[]>([]);
const [experimentalEnabled, setExperimentalEnabled] = useState(false); const [experimentalEnabled, setExperimentalEnabled] = useState(false);
const fetchFeatures = useFeaturesStore((s) => s.fetch);
useEffect(() => {
fetchFeatures();
}, [fetchFeatures]);
useEffect(() => { useEffect(() => {
apiGet<{ settings: Record<string, string> }>("/v1/settings") apiGet<{ settings: Record<string, string> }>("/v1/settings")